Merge from git-svn r115 to master branch

Conflicts:
	talk/libjingle.scons
	talk/main.scons
	talk/site_scons/talk.py

Change-Id: I173ec93b4b4a7aca68663dc756101325f280abf4
diff --git a/AUTHORS b/AUTHORS
index e491a9e..cbe3b77 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1 +1,4 @@
 Google Inc.
+Eric Rescorla, RTFM Inc.
+Pali Rohar
+Robert Nagy
diff --git a/CHANGELOG b/CHANGELOG
index 1f64bb0..ecc343f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,62 @@
 Libjingle
 
+0.6.12 - Feb 07, 2012
+  - PeerConnection client for windows.
+  - Bug fixes.
+
+0.6.11 - Jan 24, 2012
+  - Improved ipv6 support.
+  - Initial DTLS support.
+  - Initial BUNDLE support.
+  - Update Jingle protocol to multistream.
+  - WebRTC Bug fixes.
+
+0.6.10 - Jan 11, 2012
+  - Support fullscreen screencasting of secondary displays.
+  - Add IPv6 support for libjingle's STUN components.
+  - Enable SRTP in PeerConnection v1.
+  - Bug fixes.
+
+0.6.9 - Jan 09, 2012
+  - Enable SRTP in PeerConnection.
+  - Bug fixes.
+
+0.6.8 - Dec 22, 2011
+  - Add a lot of unit tests
+  - Add a lot of older files to base/ and xmpp/
+  - Add examples/pcp add examples/peerconnection
+  - Improve support for IPV6
+
+0.6.7 - Dec 21, 2011
+  - Release new PeerConnection implementation to app/webrtc.
+  - Bug fixes.
+
+0.6.6 - Dec 14, 2011
+  - Fix support for rtcp multiplexing (aka rtcp-mux).
+  - Add more support for FreeBSD and OpenBSD.
+  - Add more unit tests to session/phone.
+  - Add session/phone/mediarecorder.cc.
+  - Fixed httpportallocator tests.
+
+0.6.5 - Dec 8, 2011
+  - Add IPv6 support in SocketAddress.
+  - Change PeerConnectionFactory inteface.
+  - Bug fixes.
+
+0.6.4 - Nov 30, 2011
+  - Branch app/webrtc to app/webrtcv1.
+  - Add more base unit tests.
+  - Add xmllite unit tests.
+  - Refactoring and bug fixes
+
+0.6.3 - Oct 26, 2011
+  - Add media unit tests
+  - Improve OpenSSL support
+  - Add SSL unit tests
+  - Add DTLS support to SslStreamAdapter
+  - Add initial support for media processors
+  - Updated WebRTC voice and video engines
+
 0.6.2 - Oct 7, 2011
   - Increase the video rtp buffer.
   - Disable sound system for chromium build.
diff --git a/README b/README
index f40d4ea..638d002 100644
--- a/README
+++ b/README
@@ -95,13 +95,22 @@
     copy talk\third_party\srtp\config.hw talk\third_party\srtp\crypto\include\config.h
 
 2.2 Build Libjingle under Linux or OS X
-  * First, make sure the SCONS_DIR environment variable is set correctly.
+  * On Linux, you need to install libssl-dev, libasound2-dev and gtk+2.0.
+  * Some optional new features in OpenSSL are only available in OpenSSL v1.0
+    and above. To build with new OpenSSL features, you need to add the
+    "HAS_OPENSSL_1_0" to the cppdefine under the
+    "talk.Library(env, name = jingle..." section in the "libjingle.scons" file.
+    Then download the OpenSSL v1.0 from the following URL:
+    http://www.openssl.org/source/openssl-1.0.0e.tar.gz
+    Unzip the downloaded package to the "third_party/openssl", such that it creates
+    the directory "third_party/openssl/include/", etc.
+  * To build Libjingle, first make sure the SCONS_DIR environment variable
+    is set correctly.
   * Second, run talk/third_party/expat-2.0.1/configure and
     talk/third_party/srtp/configure.
   * Third, go to the talk/ directory and run $path_to_swtoolkit/hammer.sh. Run
     $path_to_swtoolkit/hammer.sh --help for information on how to build for
     different modes.
-  * On Linux, you need to install libssl-dev, libasound2-dev and gtk+2.0.
 
 2.3 Build Libjingle under Windows
   * First, make sure the SCONS_DIR environment variable is set correctly and
diff --git a/talk/app/webrtc/audiotrackimpl.cc b/talk/app/webrtc/audiotrackimpl.cc
new file mode 100644
index 0000000..6fcef4a
--- /dev/null
+++ b/talk/app/webrtc/audiotrackimpl.cc
@@ -0,0 +1,71 @@
+/*
+ * 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/audiotrackimpl.h"
+
+#include <string>
+
+namespace webrtc {
+
+static const char kAudioTrackKind[] = "audio";
+
+AudioTrack::AudioTrack(const std::string& label)
+    : MediaStreamTrack<LocalAudioTrackInterface>(label),
+      audio_device_(NULL) {
+}
+
+AudioTrack::AudioTrack(const std::string& label,
+                       AudioDeviceModule* audio_device)
+    : MediaStreamTrack<LocalAudioTrackInterface>(label),
+      audio_device_(audio_device) {
+}
+
+  // Get the AudioDeviceModule associated with this track.
+AudioDeviceModule* AudioTrack::GetAudioDevice() {
+  return audio_device_.get();
+}
+
+  // Implement MediaStreamTrack
+std::string AudioTrack::kind() const {
+  return kAudioTrackKind;
+}
+
+talk_base::scoped_refptr<AudioTrack> AudioTrack::CreateRemote(
+    const std::string& label) {
+  talk_base::RefCountedObject<AudioTrack>* track =
+      new talk_base::RefCountedObject<AudioTrack>(label);
+  return track;
+}
+
+talk_base::scoped_refptr<AudioTrack> AudioTrack::CreateLocal(
+    const std::string& label,
+    AudioDeviceModule* audio_device) {
+  talk_base::RefCountedObject<AudioTrack>* track =
+      new talk_base::RefCountedObject<AudioTrack>(label, audio_device);
+  return track;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/audiotrackimpl.h b/talk/app/webrtc/audiotrackimpl.h
new file mode 100644
index 0000000..df3b3cb
--- /dev/null
+++ b/talk/app/webrtc/audiotrackimpl.h
@@ -0,0 +1,70 @@
+/*
+ * 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_APP_WEBRTC_AUDIOTRACKIMPL_H_
+#define TALK_APP_WEBRTC_AUDIOTRACKIMPL_H_
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/mediatrackimpl.h"
+#include "talk/app/webrtc/notifierimpl.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/audio_device/main/interface/audio_device.h"
+#else
+#include "third_party/webrtc/files/include/audio_device.h"
+#endif
+
+namespace webrtc {
+
+class AudioTrack : public MediaStreamTrack<LocalAudioTrackInterface> {
+ public:
+  // Creates a remote audio track.
+  static talk_base::scoped_refptr<AudioTrack> CreateRemote(
+      const std::string& label);
+  // Creates a local audio track.
+  static talk_base::scoped_refptr<AudioTrack> CreateLocal(
+      const std::string& label,
+      AudioDeviceModule* audio_device);
+
+  // Get the AudioDeviceModule associated with this track.
+  virtual AudioDeviceModule* GetAudioDevice();
+
+  // Implement MediaStreamTrack
+  virtual std::string kind() const;
+
+ protected:
+  explicit AudioTrack(const std::string& label);
+  AudioTrack(const std::string& label, AudioDeviceModule* audio_device);
+
+ private:
+  talk_base::scoped_refptr<AudioDeviceModule> audio_device_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_AUDIOTRACKIMPL_H_
diff --git a/talk/app/webrtc/fakeportallocatorfactory.h b/talk/app/webrtc/fakeportallocatorfactory.h
new file mode 100644
index 0000000..85885d4
--- /dev/null
+++ b/talk/app/webrtc/fakeportallocatorfactory.h
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+// This file defines a fake port allocator factory used for testing.
+// This implementation creates instances of cricket::FakePortAllocator.
+
+#ifndef TALK_APP_WEBRTC_FAKEPORTALLOCATORFACTORY_H_
+#define TALK_APP_WEBRTC_FAKEPORTALLOCATORFACTORY_H_
+
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/p2p/client/fakeportallocator.h"
+
+namespace webrtc {
+
+class FakePortAllocatorFactory : public PortAllocatorFactoryInterface {
+ public:
+  static PortAllocatorFactoryInterface* Create() {
+    talk_base::RefCountedObject<FakePortAllocatorFactory>* allocator =
+          new talk_base::RefCountedObject<FakePortAllocatorFactory>();
+    return allocator;
+  }
+
+  virtual cricket::PortAllocator* CreatePortAllocator(
+      const std::vector<StunConfiguration>& stun_configurations,
+      const std::vector<TurnConfiguration>& turn_configurations) {
+    return new cricket::FakePortAllocator(talk_base::Thread::Current(), NULL);
+  }
+
+ protected:
+  FakePortAllocatorFactory() {}
+  ~FakePortAllocatorFactory() {}
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_FAKEPORTALLOCATORFACTORY_H_
diff --git a/talk/app/webrtc/mediastream.h b/talk/app/webrtc/mediastream.h
new file mode 100644
index 0000000..5db591a
--- /dev/null
+++ b/talk/app/webrtc/mediastream.h
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+
+// This file contains interfaces for MediaStream and MediaTrack. These
+// interfaces are used for implementing MediaStream and MediaTrack as defined
+// in http://dev.w3.org/2011/webrtc/editor/webrtc.html#stream-api. These
+// interfaces must be used only with PeerConnection. PeerConnectionManager
+// interface provides the factory methods to create MediaStream and MediaTracks.
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAM_H_
+#define TALK_APP_WEBRTC_MEDIASTREAM_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/refcount.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+namespace cricket {
+
+class VideoCapturer;
+class VideoRenderer;
+class MediaEngine;
+
+}  // namespace cricket
+
+namespace webrtc {
+
+class AudioDeviceModule;
+
+// Generic observer interface.
+class ObserverInterface {
+ public:
+  virtual void OnChanged() = 0;
+
+ protected:
+  virtual ~ObserverInterface() {}
+};
+
+class NotifierInterface {
+ public:
+  virtual void RegisterObserver(ObserverInterface* observer) = 0;
+  virtual void UnregisterObserver(ObserverInterface* observer) = 0;
+
+  virtual ~NotifierInterface() {}
+};
+
+// Information about a track.
+class MediaStreamTrackInterface : public talk_base::RefCountInterface,
+                                  public NotifierInterface {
+ public:
+  enum TrackState {
+    kInitializing,  // Track is beeing negotiated.
+    kLive = 1,  // Track alive
+    kEnded = 2,  // Track have ended
+    kFailed = 3,  // Track negotiation failed.
+  };
+
+  virtual std::string kind() const = 0;
+  virtual std::string label() const = 0;
+  virtual bool enabled() const = 0;
+  virtual TrackState state() const = 0;
+  virtual bool set_enabled(bool enable) = 0;
+  // These methods should be called by implementation only.
+  virtual bool set_state(TrackState new_state) = 0;
+};
+
+// Reference counted wrapper for a VideoRenderer.
+class VideoRendererWrapperInterface : public talk_base::RefCountInterface {
+ public:
+  virtual cricket::VideoRenderer* renderer() = 0;
+
+ protected:
+  virtual ~VideoRendererWrapperInterface() {}
+};
+
+// Creates a reference counted object of type cricket::VideoRenderer.
+// webrtc::VideoRendererWrapperInterface take ownership of
+// cricket::VideoRenderer.
+talk_base::scoped_refptr<VideoRendererWrapperInterface> CreateVideoRenderer(
+    cricket::VideoRenderer* renderer);
+
+class VideoTrackInterface : public MediaStreamTrackInterface {
+ public:
+  // Set the video renderer for a local or remote stream.
+  // This call will start decoding the received video stream and render it.
+  // The VideoRendererInterface is stored as a scoped_refptr. This means that
+  // it is not allowed to call delete renderer after this API has been called.
+  virtual void SetRenderer(VideoRendererWrapperInterface* renderer) = 0;
+
+  // Get the VideoRenderer associated with this track.
+  virtual VideoRendererWrapperInterface* GetRenderer() = 0;
+
+ protected:
+  virtual ~VideoTrackInterface() {}
+};
+
+class LocalVideoTrackInterface : public VideoTrackInterface {
+ public:
+  // Get the VideoCapturer associated with the track.
+  virtual cricket::VideoCapturer* GetVideoCapture() = 0;
+
+ protected:
+  virtual ~LocalVideoTrackInterface() {}
+};
+
+class AudioTrackInterface : public MediaStreamTrackInterface {
+ public:
+ protected:
+  virtual ~AudioTrackInterface() {}
+};
+
+class LocalAudioTrackInterface : public AudioTrackInterface {
+ public:
+  // Get the AudioDeviceModule associated with this track.
+  virtual AudioDeviceModule* GetAudioDevice() =  0;
+ protected:
+  virtual ~LocalAudioTrackInterface() {}
+};
+
+// List of of tracks.
+template <class TrackType>
+class MediaStreamTrackListInterface : public talk_base::RefCountInterface {
+ public:
+  virtual size_t count() = 0;
+  virtual TrackType* at(size_t index) = 0;
+
+ protected:
+  virtual ~MediaStreamTrackListInterface() {}
+};
+
+typedef MediaStreamTrackListInterface<AudioTrackInterface> AudioTracks;
+typedef MediaStreamTrackListInterface<VideoTrackInterface> VideoTracks;
+
+class MediaStreamInterface : public talk_base::RefCountInterface,
+                             public NotifierInterface {
+ public:
+  virtual std::string label() const = 0;
+  virtual AudioTracks* audio_tracks() = 0;
+  virtual VideoTracks* video_tracks() = 0;
+
+  enum ReadyState {
+    kInitializing,
+    kLive = 1,  // Stream alive
+    kEnded = 2,  // Stream have ended
+  };
+
+  virtual ReadyState ready_state() = 0;
+
+  // These methods should be called by implementation only.
+  virtual void set_ready_state(ReadyState state) = 0;
+
+ protected:
+  virtual ~MediaStreamInterface() {}
+};
+
+class LocalMediaStreamInterface : public MediaStreamInterface {
+ public:
+  virtual bool AddTrack(AudioTrackInterface* track) = 0;
+  virtual bool AddTrack(VideoTrackInterface* track) = 0;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAM_H_
diff --git a/talk/app/webrtc/mediastream_unittest.cc b/talk/app/webrtc/mediastream_unittest.cc
new file mode 100644
index 0000000..97d0c64
--- /dev/null
+++ b/talk/app/webrtc/mediastream_unittest.cc
@@ -0,0 +1,389 @@
+/*
+ * 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 <string>
+
+#include "talk/app/webrtc/mediastreamproxy.h"
+#include "talk/app/webrtc/mediastreamtrackproxy.h"
+#include "talk/base/refcount.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/base/gunit.h"
+#include "testing/base/public/gmock.h"
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kVideoTrackLabel[] = "dummy_video_cam_1";
+static const char kAudioTrackLabel[] = "dummy_microphone_1";
+
+using talk_base::scoped_refptr;
+using ::testing::Exactly;
+
+namespace {
+
+class ReadyStateMessageData : public talk_base::MessageData {
+ public:
+  ReadyStateMessageData(
+      webrtc::MediaStreamInterface* stream,
+      webrtc::MediaStreamInterface::ReadyState new_state)
+      : stream_(stream),
+        ready_state_(new_state) {
+  }
+
+  scoped_refptr<webrtc::MediaStreamInterface> stream_;
+  webrtc::MediaStreamInterface::ReadyState ready_state_;
+};
+
+class TrackStateMessageData : public talk_base::MessageData {
+ public:
+  TrackStateMessageData(
+      webrtc::MediaStreamTrackInterface* track,
+      webrtc::MediaStreamTrackInterface::TrackState state)
+      : track_(track),
+        state_(state) {
+  }
+
+  scoped_refptr<webrtc::MediaStreamTrackInterface> track_;
+  webrtc::MediaStreamTrackInterface::TrackState state_;
+};
+
+}  // namespace anonymous
+
+namespace webrtc {
+
+// Helper class to test Observer.
+class MockObserver : public ObserverInterface {
+ public:
+  explicit MockObserver(talk_base::Thread* signaling_thread)
+      : signaling_thread_(signaling_thread) {
+  }
+
+  MOCK_METHOD0(DoOnChanged, void());
+  virtual void OnChanged() {
+    ASSERT_TRUE(talk_base::Thread::Current() == signaling_thread_);
+    DoOnChanged();
+  }
+ private:
+  talk_base::Thread* signaling_thread_;
+};
+
+class MockMediaStream: public LocalMediaStreamInterface {
+ public:
+  MockMediaStream(const std::string& label, talk_base::Thread* signaling_thread)
+      : stream_impl_(MediaStream::Create(label)),
+        signaling_thread_(signaling_thread) {
+  }
+  virtual void RegisterObserver(webrtc::ObserverInterface* observer) {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    stream_impl_->RegisterObserver(observer);
+  }
+  virtual void UnregisterObserver(webrtc::ObserverInterface* observer) {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    stream_impl_->UnregisterObserver(observer);
+  }
+  virtual std::string label() const {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return stream_impl_->label();
+  }
+  virtual AudioTracks* audio_tracks() {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return stream_impl_->audio_tracks();
+  }
+  virtual VideoTracks* video_tracks() {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return stream_impl_->video_tracks();
+  }
+  virtual ReadyState ready_state() {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return stream_impl_->ready_state();
+  }
+  virtual void set_ready_state(ReadyState state) {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return stream_impl_->set_ready_state(state);
+  }
+  virtual bool AddTrack(AudioTrackInterface* audio_track) {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return stream_impl_->AddTrack(audio_track);
+  }
+  virtual bool AddTrack(VideoTrackInterface* video_track) {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return stream_impl_->AddTrack(video_track);
+  }
+
+ private:
+  scoped_refptr<MediaStream> stream_impl_;
+  talk_base::Thread* signaling_thread_;
+};
+
+template <class T>
+class MockMediaStreamTrack: public T {
+ public:
+  MockMediaStreamTrack(T* implementation,
+                       talk_base::Thread* signaling_thread)
+      : track_impl_(implementation),
+        signaling_thread_(signaling_thread) {
+  }
+  virtual void RegisterObserver(webrtc::ObserverInterface* observer) {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    track_impl_->RegisterObserver(observer);
+  }
+  virtual void UnregisterObserver(webrtc::ObserverInterface* observer) {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    track_impl_->UnregisterObserver(observer);
+  }
+  virtual std::string kind() const {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return track_impl_->kind();
+  }
+  virtual std::string label() const {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return track_impl_->label();
+  }
+  virtual bool enabled() const {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return track_impl_->enabled();
+  }
+  virtual MediaStreamTrackInterface::TrackState state() const {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return track_impl_->state();
+  }
+  virtual bool set_enabled(bool enabled) {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return track_impl_->set_enabled(enabled);
+  }
+  virtual bool set_state(webrtc::MediaStreamTrackInterface::TrackState state) {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return track_impl_->set_state(state);
+  }
+
+ protected:
+  scoped_refptr<T> track_impl_;
+  talk_base::Thread* signaling_thread_;
+};
+
+class MockLocalVideoTrack
+    : public MockMediaStreamTrack<LocalVideoTrackInterface> {
+ public:
+    MockLocalVideoTrack(LocalVideoTrackInterface* implementation,
+                        talk_base::Thread* signaling_thread)
+        : MockMediaStreamTrack<LocalVideoTrackInterface>(implementation,
+                                                         signaling_thread) {
+    }
+  virtual void SetRenderer(webrtc::VideoRendererWrapperInterface* renderer) {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    track_impl_->SetRenderer(renderer);
+  }
+  virtual VideoRendererWrapperInterface* GetRenderer() {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return track_impl_->GetRenderer();
+  }
+  virtual cricket::VideoCapturer* GetVideoCapture() {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return track_impl_->GetVideoCapture();
+  }
+};
+
+class MockLocalAudioTrack
+    : public MockMediaStreamTrack<LocalAudioTrackInterface> {
+ public:
+  MockLocalAudioTrack(LocalAudioTrackInterface* implementation,
+                      talk_base::Thread* signaling_thread)
+    : MockMediaStreamTrack<LocalAudioTrackInterface>(implementation,
+                                                     signaling_thread) {
+  }
+
+  virtual AudioDeviceModule* GetAudioDevice() {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+    return track_impl_->GetAudioDevice();
+  }
+};
+
+class MediaStreamTest: public testing::Test,
+                       public talk_base::MessageHandler {
+ protected:
+  virtual void SetUp() {
+    signaling_thread_ .reset(new talk_base::Thread());
+    ASSERT_TRUE(signaling_thread_->Start());
+
+    std::string label(kStreamLabel1);
+    // Create a stream proxy object that uses our mocked
+    // version of a LocalMediaStream.
+    scoped_refptr<MockMediaStream> mock_stream(
+        new talk_base::RefCountedObject<MockMediaStream>(label,
+                                                 signaling_thread_.get()));
+    stream_ = MediaStreamProxy::Create(label, signaling_thread_.get(),
+                                       mock_stream);
+    ASSERT_TRUE(stream_.get() != NULL);
+    EXPECT_EQ(label, stream_->label());
+    EXPECT_EQ(MediaStreamInterface::kInitializing, stream_->ready_state());
+
+    // Create a video track proxy object that uses our mocked
+    // version of a LocalVideoTrack
+    scoped_refptr<VideoTrack> video_track_impl(
+        VideoTrack::CreateLocal(kVideoTrackLabel, NULL));
+    scoped_refptr<MockLocalVideoTrack> mock_videotrack(
+        new talk_base::RefCountedObject<MockLocalVideoTrack>(video_track_impl,
+                                                     signaling_thread_.get()));
+    video_track_ = VideoTrackProxy::CreateLocal(mock_videotrack,
+                                                signaling_thread_.get());
+
+    ASSERT_TRUE(video_track_.get() != NULL);
+    EXPECT_EQ(MediaStreamTrackInterface::kInitializing, video_track_->state());
+
+    // Create an audio track proxy object that uses our mocked
+    // version of a LocalAudioTrack
+    scoped_refptr<AudioTrack> audio_track_impl(
+        AudioTrack::CreateLocal(kAudioTrackLabel, NULL));
+    scoped_refptr<MockLocalAudioTrack> mock_audiotrack(
+        new talk_base::RefCountedObject<MockLocalAudioTrack>(audio_track_impl,
+                                                     signaling_thread_.get()));
+    audio_track_ = AudioTrackProxy::CreateLocal(mock_audiotrack,
+                                                signaling_thread_.get());
+
+    ASSERT_TRUE(audio_track_.get() != NULL);
+    EXPECT_EQ(MediaStreamTrackInterface::kInitializing, audio_track_->state());
+  }
+
+  enum {
+    MSG_SET_READYSTATE,
+    MSG_SET_TRACKSTATE,
+  };
+
+  // Set the ready state on the signaling thread.
+  // State can only be changed on the signaling thread.
+  void SetReadyState(MediaStreamInterface* stream,
+                     MediaStreamInterface::ReadyState new_state) {
+    ReadyStateMessageData state(stream, new_state);
+    signaling_thread_->Send(this, MSG_SET_READYSTATE, &state);
+  }
+
+  // Set the track state on the signaling thread.
+  // State can only be changed on the signaling thread.
+  void SetTrackState(MediaStreamTrackInterface* track,
+                     MediaStreamTrackInterface::TrackState new_state) {
+    TrackStateMessageData state(track, new_state);
+    signaling_thread_->Send(this, MSG_SET_TRACKSTATE, &state);
+  }
+
+  talk_base::scoped_ptr<talk_base::Thread> signaling_thread_;
+  scoped_refptr<LocalMediaStreamInterface> stream_;
+  scoped_refptr<LocalVideoTrackInterface> video_track_;
+  scoped_refptr<LocalAudioTrackInterface> audio_track_;
+
+ private:
+  // Implements talk_base::MessageHandler.
+  virtual void OnMessage(talk_base::Message* msg) {
+    switch (msg->message_id) {
+      case MSG_SET_READYSTATE: {
+        ReadyStateMessageData* state =
+            static_cast<ReadyStateMessageData*>(msg->pdata);
+        state->stream_->set_ready_state(state->ready_state_);
+        break;
+      }
+      case MSG_SET_TRACKSTATE: {
+        TrackStateMessageData* state =
+            static_cast<TrackStateMessageData*>(msg->pdata);
+        state->track_->set_state(state->state_);
+        break;
+      }
+      default:
+        break;
+    }
+  }
+};
+
+TEST_F(MediaStreamTest, CreateLocalStream) {
+  EXPECT_TRUE(stream_->AddTrack(video_track_));
+  EXPECT_TRUE(stream_->AddTrack(audio_track_));
+
+  ASSERT_EQ(1u, stream_->video_tracks()->count());
+  ASSERT_EQ(1u, stream_->audio_tracks()->count());
+
+  // Verify the video track.
+  scoped_refptr<webrtc::MediaStreamTrackInterface> track(
+      stream_->video_tracks()->at(0));
+  EXPECT_EQ(0, track->label().compare(kVideoTrackLabel));
+  EXPECT_TRUE(track->enabled());
+
+  // Verify the audio track.
+  track = stream_->audio_tracks()->at(0);
+  EXPECT_EQ(0, track->label().compare(kAudioTrackLabel));
+  EXPECT_TRUE(track->enabled());
+}
+
+TEST_F(MediaStreamTest, ChangeStreamState) {
+  MockObserver observer(signaling_thread_.get());
+  stream_->RegisterObserver(&observer);
+
+  EXPECT_CALL(observer, DoOnChanged())
+      .Times(Exactly(1));
+  SetReadyState(stream_, MediaStreamInterface::kLive);
+
+  EXPECT_EQ(MediaStreamInterface::kLive, stream_->ready_state());
+  // It should not be possible to add
+  // streams when the state has changed to live.
+  EXPECT_FALSE(stream_->AddTrack(audio_track_));
+  EXPECT_EQ(0u, stream_->audio_tracks()->count());
+}
+
+TEST_F(MediaStreamTest, ChangeVideoTrack) {
+  MockObserver observer(signaling_thread_.get());
+  video_track_->RegisterObserver(&observer);
+
+  EXPECT_CALL(observer, DoOnChanged())
+      .Times(Exactly(1));
+  video_track_->set_enabled(false);
+  EXPECT_FALSE(video_track_->state());
+
+  EXPECT_CALL(observer, DoOnChanged())
+      .Times(Exactly(1));
+  SetTrackState(video_track_, MediaStreamTrackInterface::kLive);
+  EXPECT_EQ(MediaStreamTrackInterface::kLive, video_track_->state());
+
+  EXPECT_CALL(observer, DoOnChanged())
+      .Times(Exactly(1));
+  scoped_refptr<VideoRendererWrapperInterface> renderer(
+      CreateVideoRenderer(NULL));
+  video_track_->SetRenderer(renderer.get());
+  EXPECT_TRUE(renderer.get() == video_track_->GetRenderer());
+}
+
+TEST_F(MediaStreamTest, ChangeAudioTrack) {
+  MockObserver observer(signaling_thread_.get());
+  audio_track_->RegisterObserver(&observer);
+
+  EXPECT_CALL(observer, DoOnChanged())
+      .Times(Exactly(1));
+  audio_track_->set_enabled(false);
+  EXPECT_FALSE(audio_track_->enabled());
+
+  EXPECT_CALL(observer, DoOnChanged())
+      .Times(Exactly(1));
+  SetTrackState(audio_track_, MediaStreamTrackInterface::kLive);
+  EXPECT_EQ(MediaStreamTrackInterface::kLive, audio_track_->state());
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamhandler.cc b/talk/app/webrtc/mediastreamhandler.cc
new file mode 100644
index 0000000..d120b80
--- /dev/null
+++ b/talk/app/webrtc/mediastreamhandler.cc
@@ -0,0 +1,258 @@
+/*
+ * 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/app/webrtc/mediastreamhandler.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_capture/main/interface/video_capture.h"
+#else
+#include "third_party/webrtc/files/include/video_capture.h"
+#endif
+
+namespace webrtc {
+
+VideoTrackHandler::VideoTrackHandler(VideoTrackInterface* track,
+                                     MediaProviderInterface* provider)
+    : provider_(provider),
+      video_track_(track),
+      state_(track->state()),
+      enabled_(track->enabled()),
+      renderer_(track->GetRenderer()) {
+  video_track_->RegisterObserver(this);
+}
+
+VideoTrackHandler::~VideoTrackHandler() {
+  video_track_->UnregisterObserver(this);
+}
+
+void VideoTrackHandler::OnChanged() {
+  if (state_ != video_track_->state()) {
+    state_ = video_track_->state();
+    OnStateChanged();
+  }
+  if (renderer_.get() != video_track_->GetRenderer()) {
+    renderer_ = video_track_->GetRenderer();
+    OnRendererChanged();
+  }
+  if (enabled_ != video_track_->enabled()) {
+    enabled_ = video_track_->enabled();
+    OnEnabledChanged();
+  }
+}
+
+LocalVideoTrackHandler::LocalVideoTrackHandler(
+    LocalVideoTrackInterface* track,
+    MediaProviderInterface* provider)
+    : VideoTrackHandler(track, provider),
+      local_video_track_(track) {
+}
+
+LocalVideoTrackHandler::~LocalVideoTrackHandler() {
+  // cricket::VideoRenderer and cricket::VideoCapturer is owned and deleted by
+  // the track. It must be removed from the media stream provider since it is
+  // possible that the tracks reference count is set to zero when
+  // local_video_track_ falls out of scope.
+  provider_->SetLocalRenderer(local_video_track_->label(), NULL);
+  provider_->SetCaptureDevice(local_video_track_->label(), NULL);
+}
+
+void LocalVideoTrackHandler::OnRendererChanged() {
+  VideoRendererWrapperInterface* renderer = video_track_->GetRenderer();
+  if (renderer)
+    provider_->SetLocalRenderer(video_track_->label(), renderer->renderer());
+  else
+    provider_->SetLocalRenderer(video_track_->label(), NULL);
+}
+
+void LocalVideoTrackHandler::OnStateChanged() {
+  if (local_video_track_->state() == VideoTrackInterface::kLive) {
+    provider_->SetCaptureDevice(local_video_track_->label(),
+                                local_video_track_->GetVideoCapture());
+    VideoRendererWrapperInterface* renderer = video_track_->GetRenderer();
+    if (renderer)
+      provider_->SetLocalRenderer(video_track_->label(), renderer->renderer());
+    else
+      provider_->SetLocalRenderer(video_track_->label(), NULL);
+  }
+}
+
+void LocalVideoTrackHandler::OnEnabledChanged() {
+  // TODO What should happen when enabled is changed?
+}
+
+RemoteVideoTrackHandler::RemoteVideoTrackHandler(
+    VideoTrackInterface* track,
+    MediaProviderInterface* provider)
+    : VideoTrackHandler(track, provider),
+      remote_video_track_(track) {
+}
+
+RemoteVideoTrackHandler::~RemoteVideoTrackHandler() {
+  // Since cricket::VideoRenderer is not reference counted
+  // we need to remove the renderer before we are deleted.
+  provider_->SetRemoteRenderer(video_track_->label(), NULL);
+}
+
+
+void RemoteVideoTrackHandler::OnRendererChanged() {
+  VideoRendererWrapperInterface* renderer = video_track_->GetRenderer();
+  if (renderer)
+    provider_->SetRemoteRenderer(video_track_->label(), renderer->renderer());
+  else
+    provider_->SetRemoteRenderer(video_track_->label(), NULL);
+}
+
+void RemoteVideoTrackHandler::OnStateChanged() {
+}
+
+void RemoteVideoTrackHandler::OnEnabledChanged() {
+  // TODO: What should happen when enabled is changed?
+}
+
+MediaStreamHandler::MediaStreamHandler(MediaStreamInterface* stream,
+                                       MediaProviderInterface* provider)
+    : stream_(stream),
+      provider_(provider) {
+}
+
+MediaStreamHandler::~MediaStreamHandler() {
+  for (VideoTrackHandlers::iterator it = video_handlers_.begin();
+       it != video_handlers_.end(); ++it) {
+    delete *it;
+  }
+}
+
+MediaStreamInterface* MediaStreamHandler::stream() {
+  return stream_.get();
+}
+
+void MediaStreamHandler::OnChanged() {
+  // TODO: Implement state change and enabled changed.
+}
+
+
+LocalMediaStreamHandler::LocalMediaStreamHandler(
+    MediaStreamInterface* stream,
+    MediaProviderInterface* provider)
+    : MediaStreamHandler(stream, provider) {
+  VideoTracks* tracklist(stream->video_tracks());
+
+  for (size_t j = 0; j < tracklist->count(); ++j) {
+    LocalVideoTrackInterface* track =
+        static_cast<LocalVideoTrackInterface*>(tracklist->at(j));
+    VideoTrackHandler* handler(new LocalVideoTrackHandler(track, provider));
+    video_handlers_.push_back(handler);
+  }
+}
+
+RemoteMediaStreamHandler::RemoteMediaStreamHandler(
+    MediaStreamInterface* stream,
+    MediaProviderInterface* provider)
+    : MediaStreamHandler(stream, provider) {
+  VideoTracks* tracklist(stream->video_tracks());
+
+  for (size_t j = 0; j < tracklist->count(); ++j) {
+    VideoTrackInterface* track =
+        static_cast<VideoTrackInterface*>(tracklist->at(j));
+    VideoTrackHandler* handler(new RemoteVideoTrackHandler(track, provider));
+    video_handlers_.push_back(handler);
+  }
+}
+
+MediaStreamHandlers::MediaStreamHandlers(MediaProviderInterface* provider)
+    : provider_(provider) {
+}
+
+MediaStreamHandlers::~MediaStreamHandlers() {
+  for (StreamHandlerList::iterator it = remote_streams_handlers_.begin();
+       it != remote_streams_handlers_.end(); ++it) {
+    delete *it;
+  }
+  for (StreamHandlerList::iterator it = local_streams_handlers_.begin();
+       it != local_streams_handlers_.end(); ++it) {
+    delete *it;
+  }
+}
+
+void MediaStreamHandlers::AddRemoteStream(MediaStreamInterface* stream) {
+  RemoteMediaStreamHandler* handler = new RemoteMediaStreamHandler(stream,
+                                                                   provider_);
+  remote_streams_handlers_.push_back(handler);
+}
+
+void MediaStreamHandlers::RemoveRemoteStream(MediaStreamInterface* stream) {
+  StreamHandlerList::iterator it = remote_streams_handlers_.begin();
+  for (; it != remote_streams_handlers_.end(); ++it) {
+    if ((*it)->stream() == stream) {
+      delete *it;
+      break;
+    }
+  }
+  ASSERT(it != remote_streams_handlers_.end());
+  remote_streams_handlers_.erase(it);
+}
+
+void MediaStreamHandlers::CommitLocalStreams(
+    StreamCollectionInterface* streams) {
+  // Iterate the old list of local streams.
+  // If its not found in the new collection it have been removed.
+  // We can not erase from the old collection at the same time as we iterate.
+  // That is what the ugly while(1) fix.
+  while (1) {
+    StreamHandlerList::iterator it = local_streams_handlers_.begin();
+    for (; it != local_streams_handlers_.end(); ++it) {
+      if (streams->find((*it)->stream()->label()) == NULL) {
+        delete *it;
+        break;
+      }
+    }
+    if (it != local_streams_handlers_.end()) {
+      local_streams_handlers_.erase(it);
+      continue;
+    }
+    break;
+  }
+
+  // Iterate the new collection of local streams.
+  // If its not found in the old collection it have been added.
+  for (size_t j = 0; j < streams->count(); ++j) {
+    MediaStreamInterface* stream = streams->at(j);
+    StreamHandlerList::iterator it = local_streams_handlers_.begin();
+    for (; it != local_streams_handlers_.end(); ++it) {
+      if (stream == (*it)->stream())
+        break;
+    }
+    if (it == local_streams_handlers_.end()) {
+      LocalMediaStreamHandler* handler = new LocalMediaStreamHandler(
+          stream, provider_);
+      local_streams_handlers_.push_back(handler);
+    }
+  }
+};
+
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamhandler.h b/talk/app/webrtc/mediastreamhandler.h
new file mode 100644
index 0000000..550a2d4
--- /dev/null
+++ b/talk/app/webrtc/mediastreamhandler.h
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+// This file contains classes for listening on changes on MediaStreams and
+// MediaTracks and making sure appropriate action is taken.
+// Example: If a user sets a rendererer on a local video track the renderer is
+// connected to the appropriate camera.
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAMHANDLER_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMHANDLER_H_
+
+#include <list>
+#include <vector>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/mediastreamprovider.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/base/thread.h"
+
+namespace webrtc {
+
+// VideoTrackHandler listen to events on a VideoTrack instance and
+// executes the requested change.
+class VideoTrackHandler : public ObserverInterface {
+ public:
+  VideoTrackHandler(VideoTrackInterface* track,
+                    MediaProviderInterface* provider);
+  virtual ~VideoTrackHandler();
+  virtual void OnChanged();
+
+ protected:
+  virtual void OnRendererChanged() = 0;
+  virtual void OnStateChanged() = 0;
+  virtual void OnEnabledChanged() = 0;
+
+  MediaProviderInterface* provider_;
+  VideoTrackInterface* video_track_;
+
+ private:
+  MediaStreamTrackInterface::TrackState state_;
+  bool enabled_;
+  talk_base::scoped_refptr<VideoRendererWrapperInterface> renderer_;
+};
+
+class LocalVideoTrackHandler : public VideoTrackHandler {
+ public:
+  LocalVideoTrackHandler(LocalVideoTrackInterface* track,
+                         MediaProviderInterface* provider);
+  virtual ~LocalVideoTrackHandler();
+
+ protected:
+  virtual void OnRendererChanged();
+  virtual void OnStateChanged();
+  virtual void OnEnabledChanged();
+
+ private:
+  talk_base::scoped_refptr<LocalVideoTrackInterface> local_video_track_;
+};
+
+class RemoteVideoTrackHandler : public VideoTrackHandler {
+ public:
+  RemoteVideoTrackHandler(VideoTrackInterface* track,
+                          MediaProviderInterface* provider);
+  virtual ~RemoteVideoTrackHandler();
+
+ protected:
+  virtual void OnRendererChanged();
+  virtual void OnStateChanged();
+  virtual void OnEnabledChanged();
+
+ private:
+  talk_base::scoped_refptr<VideoTrackInterface> remote_video_track_;
+};
+
+class MediaStreamHandler : public ObserverInterface {
+ public:
+  MediaStreamHandler(MediaStreamInterface* stream,
+                     MediaProviderInterface* provider);
+  ~MediaStreamHandler();
+  MediaStreamInterface* stream();
+  virtual void OnChanged();
+
+ protected:
+  talk_base::scoped_refptr<MediaStreamInterface> stream_;
+  MediaProviderInterface* provider_;
+  typedef std::vector<VideoTrackHandler*> VideoTrackHandlers;
+  VideoTrackHandlers video_handlers_;
+};
+
+class LocalMediaStreamHandler : public MediaStreamHandler {
+ public:
+  LocalMediaStreamHandler(MediaStreamInterface* stream,
+                          MediaProviderInterface* provider);
+};
+
+class RemoteMediaStreamHandler : public MediaStreamHandler {
+ public:
+  RemoteMediaStreamHandler(MediaStreamInterface* stream,
+                           MediaProviderInterface* provider);
+};
+
+class MediaStreamHandlers {
+ public:
+  explicit MediaStreamHandlers(MediaProviderInterface* provider);
+  ~MediaStreamHandlers();
+  void AddRemoteStream(MediaStreamInterface* stream);
+  void RemoveRemoteStream(MediaStreamInterface* stream);
+  void CommitLocalStreams(StreamCollectionInterface* streams);
+
+ private:
+  typedef std::list<MediaStreamHandler*> StreamHandlerList;
+  StreamHandlerList local_streams_handlers_;
+  StreamHandlerList remote_streams_handlers_;
+  MediaProviderInterface* provider_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMHANDLER_H_
+
diff --git a/talk/app/webrtc/mediastreamhandler_unittest.cc b/talk/app/webrtc/mediastreamhandler_unittest.cc
new file mode 100644
index 0000000..cd702bd
--- /dev/null
+++ b/talk/app/webrtc/mediastreamhandler_unittest.cc
@@ -0,0 +1,148 @@
+/*
+ * 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 <string>
+
+#include "talk/app/webrtc/mediastreamimpl.h"
+#include "talk/app/webrtc/videotrackimpl.h"
+#include "talk/app/webrtc/mediastreamhandler.h"
+#include "talk/app/webrtc/streamcollectionimpl.h"
+#include "talk/base/thread.h"
+#include "talk/base/gunit.h"
+#include "testing/base/public/gmock.h"
+
+using ::testing::Exactly;
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kVideoDeviceName[] = "dummy_video_cam_1";
+
+namespace webrtc {
+
+// Helper class to test MediaStreamHandler.
+class MockMediaProvier : public MediaProviderInterface {
+ public:
+  MOCK_METHOD1(SetCaptureDevice, bool(const std::string& name));
+  MOCK_METHOD1(SetLocalRenderer, void(const std::string& name));
+  MOCK_METHOD1(SetRemoteRenderer, void(const std::string& name));
+
+  virtual bool SetCaptureDevice(const std::string& name,
+                                cricket::VideoCapturer* camera) {
+    return SetCaptureDevice(name);
+  }
+  virtual void SetLocalRenderer(const std::string& name,
+                                cricket::VideoRenderer* renderer) {
+    SetLocalRenderer(name);
+  }
+
+  virtual void SetRemoteRenderer(const std::string& name,
+                                 cricket::VideoRenderer* renderer) {
+    SetRemoteRenderer(name);
+  }
+  ~MockMediaProvier() {}
+};
+
+TEST(MediaStreamHandlerTest, LocalStreams) {
+  // Create a local stream.
+  std::string label(kStreamLabel1);
+  talk_base::scoped_refptr<LocalMediaStreamInterface> stream(
+      MediaStream::Create(label));
+  talk_base::scoped_refptr<LocalVideoTrackInterface>
+      video_track(VideoTrack::CreateLocal(kVideoDeviceName, NULL));
+  EXPECT_TRUE(stream->AddTrack(video_track));
+  talk_base::scoped_refptr<VideoRendererWrapperInterface> renderer(
+      CreateVideoRenderer(NULL));
+  video_track->SetRenderer(renderer);
+
+  MockMediaProvier provider;
+  MediaStreamHandlers handlers(&provider);
+
+  talk_base::scoped_refptr<StreamCollection> collection(
+      StreamCollection::Create());
+  collection->AddStream(stream);
+
+  EXPECT_CALL(provider, SetLocalRenderer(kVideoDeviceName))
+      .Times(Exactly(2));  // SetLocalRender will also be called from dtor of
+                           // LocalVideoTrackHandler
+  EXPECT_CALL(provider, SetCaptureDevice(kVideoDeviceName))
+      .Times(Exactly(2)); // SetCaptureDevice will also be called from dtor of
+                          // LocalVideoTrackHandler
+  handlers.CommitLocalStreams(collection);
+
+  video_track->set_state(MediaStreamTrackInterface::kLive);
+  // Process posted messages.
+  talk_base::Thread::Current()->ProcessMessages(1);
+
+  collection->RemoveStream(stream);
+  handlers.CommitLocalStreams(collection);
+
+  video_track->set_state(MediaStreamTrackInterface::kEnded);
+  // Process posted messages.
+  talk_base::Thread::Current()->ProcessMessages(1);
+}
+
+TEST(MediaStreamHandlerTest, RemoteStreams) {
+  // Create a local stream. We use local stream in this test as well because
+  // they are easier to create.
+  // LocalMediaStreams inherit from MediaStreams.
+  std::string label(kStreamLabel1);
+  talk_base::scoped_refptr<LocalMediaStreamInterface> stream(
+      MediaStream::Create(label));
+  talk_base::scoped_refptr<LocalVideoTrackInterface>
+      video_track(VideoTrack::CreateLocal(kVideoDeviceName, NULL));
+  EXPECT_TRUE(stream->AddTrack(video_track));
+
+  MockMediaProvier provider;
+  MediaStreamHandlers handlers(&provider);
+
+  handlers.AddRemoteStream(stream);
+
+  EXPECT_CALL(provider, SetRemoteRenderer(kVideoDeviceName))
+      .Times(Exactly(3));  // SetRemoteRenderer is also called from dtor of
+                           // RemoteVideoTrackHandler.
+
+  // Set the renderer once.
+  talk_base::scoped_refptr<VideoRendererWrapperInterface> renderer(
+      CreateVideoRenderer(NULL));
+    video_track->SetRenderer(renderer);
+  talk_base::Thread::Current()->ProcessMessages(1);
+
+  // Change the already set renderer.
+  renderer = CreateVideoRenderer(NULL);
+    video_track->SetRenderer(renderer);
+  talk_base::Thread::Current()->ProcessMessages(1);
+
+  handlers.RemoveRemoteStream(stream);
+
+  // Change the renderer after the stream have been removed from handler.
+  // This should not trigger a call to SetRemoteRenderer.
+  renderer = CreateVideoRenderer(NULL);
+    video_track->SetRenderer(renderer);
+  // Process posted messages.
+  talk_base::Thread::Current()->ProcessMessages(1);
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamimpl.cc b/talk/app/webrtc/mediastreamimpl.cc
new file mode 100644
index 0000000..8920379
--- /dev/null
+++ b/talk/app/webrtc/mediastreamimpl.cc
@@ -0,0 +1,73 @@
+/*
+ * 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/app/webrtc/mediastreamimpl.h"
+#include "talk/base/logging.h"
+
+namespace webrtc {
+
+talk_base::scoped_refptr<MediaStream> MediaStream::Create(
+    const std::string& label) {
+  talk_base::RefCountedObject<MediaStream>* stream =
+      new talk_base::RefCountedObject<MediaStream>(label);
+  return stream;
+}
+
+MediaStream::MediaStream(const std::string& label)
+    : label_(label),
+      ready_state_(MediaStreamInterface::kInitializing),
+      audio_track_list_(
+          new talk_base::RefCountedObject<
+          MediaStreamTrackList<AudioTrackInterface> >()),
+      video_track_list_(
+          new talk_base::RefCountedObject<
+          MediaStreamTrackList<VideoTrackInterface> >()) {
+}
+
+void MediaStream::set_ready_state(
+    MediaStreamInterface::ReadyState new_state) {
+  if (ready_state_ != new_state) {
+    ready_state_ = new_state;
+    Notifier<LocalMediaStreamInterface>::FireOnChanged();
+  }
+}
+
+bool MediaStream::AddTrack(AudioTrackInterface* track) {
+  if (ready_state() != kInitializing)
+    return false;
+  audio_track_list_->AddTrack(track);
+  return true;
+}
+
+bool MediaStream::AddTrack(VideoTrackInterface* track) {
+  if (ready_state() != kInitializing)
+    return false;
+  video_track_list_->AddTrack(track);
+  return true;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamimpl.h b/talk/app/webrtc/mediastreamimpl.h
new file mode 100644
index 0000000..44ede9b
--- /dev/null
+++ b/talk/app/webrtc/mediastreamimpl.h
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+// This file contains the implementation of MediaStreamInterface interface.
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAMIMPL_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMIMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/notifierimpl.h"
+
+namespace webrtc {
+class AudioTrack;
+class VideoTrack;
+
+class MediaStream : public Notifier<LocalMediaStreamInterface> {
+ public:
+  template<class T>
+  class MediaStreamTrackList : public MediaStreamTrackListInterface<T> {
+   public:
+    void AddTrack(T* track) {
+      tracks_.push_back(track);
+    }
+    virtual size_t count() { return tracks_.size(); }
+    virtual T* at(size_t index) {
+      return tracks_.at(index);
+    }
+
+   private:
+    std::vector<talk_base::scoped_refptr<T> > tracks_;
+  };
+
+  static talk_base::scoped_refptr<MediaStream> Create(const std::string& label);
+
+  // Implement LocalMediaStreamInterface.
+  virtual bool AddTrack(AudioTrackInterface* track);
+  virtual bool AddTrack(VideoTrackInterface* track);
+  // Implement MediaStreamInterface.
+  virtual std::string label() const { return label_; }
+  virtual MediaStreamTrackListInterface<AudioTrackInterface>* audio_tracks() {
+    return audio_track_list_;
+  }
+  virtual MediaStreamTrackListInterface<VideoTrackInterface>* video_tracks() {
+    return video_track_list_;
+  }
+  virtual ReadyState ready_state() { return ready_state_; }
+  virtual void set_ready_state(ReadyState new_state);
+
+ protected:
+  explicit MediaStream(const std::string& label);
+
+  std::string label_;
+  MediaStreamInterface::ReadyState ready_state_;
+  talk_base::scoped_refptr<MediaStreamTrackList<AudioTrackInterface> >
+      audio_track_list_;
+  talk_base::scoped_refptr<MediaStreamTrackList<VideoTrackInterface> >
+      video_track_list_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMIMPL_H_
diff --git a/talk/app/webrtc/mediastreamprovider.h b/talk/app/webrtc/mediastreamprovider.h
new file mode 100644
index 0000000..e99b115
--- /dev/null
+++ b/talk/app/webrtc/mediastreamprovider.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_APP_WEBRTC_MEDIASTREAMPROVIDER_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMPROVIDER_H_
+
+#include "talk/app/webrtc/mediastream.h"
+
+namespace cricket {
+
+class VideoCapturer;
+
+}  // namespace cricket
+
+namespace webrtc {
+
+// Interface for setting media devices on a certain MediaTrack.
+// This interface is called by classes in mediastreamhandler.h to
+// set new devices.
+class MediaProviderInterface {
+ public:
+  virtual bool SetCaptureDevice(const std::string& name,
+                                cricket::VideoCapturer* camera) = 0;
+  virtual void SetLocalRenderer(const std::string& name,
+                                cricket::VideoRenderer* renderer) = 0;
+  virtual void SetRemoteRenderer(const std::string& name,
+                                 cricket::VideoRenderer* renderer) = 0;
+ protected:
+  virtual ~MediaProviderInterface() {}
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMPROVIDER_H_
diff --git a/talk/app/webrtc/mediastreamproxy.cc b/talk/app/webrtc/mediastreamproxy.cc
new file mode 100644
index 0000000..7e74c86
--- /dev/null
+++ b/talk/app/webrtc/mediastreamproxy.cc
@@ -0,0 +1,318 @@
+/*
+ * 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/app/webrtc/mediastreamproxy.h"
+#include "talk/base/refcount.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+namespace {
+
+enum {
+  MSG_SET_TRACKLIST_IMPLEMENTATION = 1,
+  MSG_REGISTER_OBSERVER,
+  MSG_UNREGISTER_OBSERVER,
+  MSG_LABEL,
+  MSG_ADD_AUDIO_TRACK,
+  MSG_ADD_VIDEO_TRACK,
+  MSG_READY_STATE,
+  MSG_COUNT,
+  MSG_AT
+};
+
+typedef talk_base::TypedMessageData<std::string*> LabelMessageData;
+typedef talk_base::TypedMessageData<size_t> SizeTMessageData;
+typedef talk_base::TypedMessageData<webrtc::ObserverInterface*>
+    ObserverMessageData;
+typedef talk_base::TypedMessageData<webrtc::MediaStreamInterface::ReadyState>
+    ReadyStateMessageData;
+
+template<typename T>
+class MediaStreamTrackMessageData : public talk_base::MessageData {
+ public:
+  explicit MediaStreamTrackMessageData(T* track)
+      : track_(track),
+        result_(false) {
+  }
+
+  talk_base::scoped_refptr<T> track_;
+  bool result_;
+};
+
+typedef MediaStreamTrackMessageData<webrtc::AudioTrackInterface>
+    AudioTrackMsgData;
+typedef MediaStreamTrackMessageData<webrtc::VideoTrackInterface>
+    VideoTrackMsgData;
+
+template <class TrackType>
+class MediaStreamTrackAtMessageData : public talk_base::MessageData {
+ public:
+  explicit MediaStreamTrackAtMessageData(size_t index)
+      : index_(index) {
+  }
+
+  size_t index_;
+  talk_base::scoped_refptr<TrackType> track_;
+};
+
+class MediaStreamTrackListsMessageData : public talk_base::MessageData {
+ public:
+  talk_base::scoped_refptr<webrtc::AudioTracks> audio_tracks_;
+  talk_base::scoped_refptr<webrtc::VideoTracks> video_tracks_;
+};
+
+}  // namespace anonymous
+
+namespace webrtc {
+
+talk_base::scoped_refptr<MediaStreamProxy> MediaStreamProxy::Create(
+    const std::string& label,
+    talk_base::Thread* signaling_thread) {
+  ASSERT(signaling_thread != NULL);
+  talk_base::RefCountedObject<MediaStreamProxy>* stream =
+      new talk_base::RefCountedObject<MediaStreamProxy>(
+          label, signaling_thread,
+          reinterpret_cast<LocalMediaStreamInterface*>(NULL));
+  return stream;
+}
+
+talk_base::scoped_refptr<MediaStreamProxy> MediaStreamProxy::Create(
+    const std::string& label,
+    talk_base::Thread* signaling_thread,
+    LocalMediaStreamInterface* media_stream_impl) {
+  ASSERT(signaling_thread != NULL);
+  ASSERT(media_stream_impl != NULL);
+  talk_base::RefCountedObject<MediaStreamProxy>* stream =
+      new talk_base::RefCountedObject<MediaStreamProxy>(label, signaling_thread,
+                                                media_stream_impl);
+  return stream;
+}
+
+MediaStreamProxy::MediaStreamProxy(const std::string& label,
+                                   talk_base::Thread* signaling_thread,
+                                   LocalMediaStreamInterface* media_stream_impl)
+    : signaling_thread_(signaling_thread),
+      media_stream_impl_(media_stream_impl),
+      audio_tracks_(new talk_base::RefCountedObject<
+                        MediaStreamTrackListProxy<AudioTrackInterface> >(
+                              signaling_thread_)),
+      video_tracks_(new talk_base::RefCountedObject<
+                        MediaStreamTrackListProxy<VideoTrackInterface> >(
+                            signaling_thread_)) {
+  if (media_stream_impl_ == NULL) {
+    media_stream_impl_ = MediaStream::Create(label);
+  }
+
+  MediaStreamTrackListsMessageData tracklists;
+  Send(MSG_SET_TRACKLIST_IMPLEMENTATION, &tracklists);
+  audio_tracks_->SetImplementation(tracklists.audio_tracks_);
+  video_tracks_->SetImplementation(tracklists.video_tracks_);
+}
+
+std::string MediaStreamProxy::label() const {
+  if (!signaling_thread_->IsCurrent()) {
+    std::string label;
+    LabelMessageData msg(&label);
+    Send(MSG_LABEL, &msg);
+    return label;
+  }
+  return media_stream_impl_->label();
+}
+
+MediaStreamInterface::ReadyState MediaStreamProxy::ready_state() {
+  if (!signaling_thread_->IsCurrent()) {
+    ReadyStateMessageData msg(MediaStreamInterface::kInitializing);
+    Send(MSG_READY_STATE, &msg);
+    return msg.data();
+  }
+  return media_stream_impl_->ready_state();
+}
+
+void MediaStreamProxy::set_ready_state(
+    MediaStreamInterface::ReadyState new_state) {
+  if (!signaling_thread_->IsCurrent()) {
+    // State should only be allowed to be changed from the signaling thread.
+    ASSERT(!"Not Allowed!");
+    return;
+  }
+  media_stream_impl_->set_ready_state(new_state);
+}
+
+bool MediaStreamProxy::AddTrack(AudioTrackInterface* track) {
+  if (!signaling_thread_->IsCurrent()) {
+    AudioTrackMsgData msg(track);
+    Send(MSG_ADD_AUDIO_TRACK, &msg);
+    return msg.result_;
+  }
+  return media_stream_impl_->AddTrack(track);
+}
+
+bool MediaStreamProxy::AddTrack(VideoTrackInterface* track) {
+  if (!signaling_thread_->IsCurrent()) {
+    VideoTrackMsgData msg(track);
+    Send(MSG_ADD_VIDEO_TRACK, &msg);
+    return msg.result_;
+  }
+  return media_stream_impl_->AddTrack(track);
+}
+
+void MediaStreamProxy::RegisterObserver(ObserverInterface* observer) {
+  if (!signaling_thread_->IsCurrent()) {
+    ObserverMessageData msg(observer);
+    Send(MSG_REGISTER_OBSERVER, &msg);
+    return;
+  }
+  media_stream_impl_->RegisterObserver(observer);
+}
+
+void MediaStreamProxy::UnregisterObserver(ObserverInterface* observer) {
+  if (!signaling_thread_->IsCurrent()) {
+    ObserverMessageData msg(observer);
+    Send(MSG_UNREGISTER_OBSERVER, &msg);
+    return;
+  }
+  media_stream_impl_->UnregisterObserver(observer);
+}
+
+void MediaStreamProxy::Send(uint32 id, talk_base::MessageData* data) const {
+  signaling_thread_->Send(const_cast<MediaStreamProxy*>(this), id,
+                          data);
+}
+
+// Implement MessageHandler
+void MediaStreamProxy::OnMessage(talk_base::Message* msg) {
+  talk_base::MessageData* data = msg->pdata;
+  switch (msg->message_id) {
+    case MSG_SET_TRACKLIST_IMPLEMENTATION: {
+      MediaStreamTrackListsMessageData* lists =
+          static_cast<MediaStreamTrackListsMessageData*>(data);
+      lists->audio_tracks_ = media_stream_impl_->audio_tracks();
+      lists->video_tracks_ = media_stream_impl_->video_tracks();
+      break;
+    }
+    case MSG_REGISTER_OBSERVER: {
+      ObserverMessageData* observer = static_cast<ObserverMessageData*>(data);
+      media_stream_impl_->RegisterObserver(observer->data());
+      break;
+    }
+    case MSG_UNREGISTER_OBSERVER: {
+      ObserverMessageData* observer = static_cast<ObserverMessageData*>(data);
+      media_stream_impl_->UnregisterObserver(observer->data());
+      break;
+    }
+    case MSG_LABEL: {
+      LabelMessageData * label = static_cast<LabelMessageData*>(data);
+      *(label->data()) = media_stream_impl_->label();
+      break;
+    }
+    case MSG_ADD_AUDIO_TRACK: {
+      AudioTrackMsgData * track =
+          static_cast<AudioTrackMsgData *>(data);
+      track->result_ = media_stream_impl_->AddTrack(track->track_.get());
+      break;
+    }
+    case MSG_ADD_VIDEO_TRACK: {
+      VideoTrackMsgData * track =
+          static_cast<VideoTrackMsgData *>(data);
+      track->result_ = media_stream_impl_->AddTrack(track->track_.get());
+      break;
+    }
+    case MSG_READY_STATE: {
+      ReadyStateMessageData* state = static_cast<ReadyStateMessageData*>(data);
+      state->data() = media_stream_impl_->ready_state();
+      break;
+    }
+    default:
+      ASSERT(!"Not Implemented!");
+      break;
+  }
+}
+
+template <class T>
+MediaStreamProxy::MediaStreamTrackListProxy<T>::MediaStreamTrackListProxy(
+    talk_base::Thread* signaling_thread)
+    : signaling_thread_(signaling_thread) {
+}
+
+template <class T>
+void MediaStreamProxy::MediaStreamTrackListProxy<T>::SetImplementation(
+    MediaStreamTrackListInterface<T>* track_list) {
+  track_list_ = track_list;
+}
+
+template <class T>
+size_t MediaStreamProxy::MediaStreamTrackListProxy<T>::count() {
+  if (!signaling_thread_->IsCurrent()) {
+    SizeTMessageData msg(0u);
+    Send(MSG_COUNT, &msg);
+    return msg.data();
+  }
+  return track_list_->count();
+}
+
+template <class T>
+T* MediaStreamProxy::MediaStreamTrackListProxy<T>::at(
+    size_t index) {
+  if (!signaling_thread_->IsCurrent()) {
+    MediaStreamTrackAtMessageData<T> msg(index);
+    Send(MSG_AT, &msg);
+    return msg.track_;
+  }
+  return track_list_->at(index);
+}
+
+template <class T>
+void MediaStreamProxy::MediaStreamTrackListProxy<T>::Send(
+    uint32 id, talk_base::MessageData* data) const {
+  signaling_thread_->Send(
+      const_cast<MediaStreamProxy::MediaStreamTrackListProxy<T>*>(
+          this), id, data);
+}
+
+// Implement MessageHandler
+template <class T>
+void MediaStreamProxy::MediaStreamTrackListProxy<T>::OnMessage(
+    talk_base::Message* msg) {
+  talk_base::MessageData* data = msg->pdata;
+  switch (msg->message_id) {
+    case MSG_COUNT: {
+      SizeTMessageData* count = static_cast<SizeTMessageData*>(data);
+      count->data() = track_list_->count();
+      break;
+    }
+    case MSG_AT: {
+      MediaStreamTrackAtMessageData<T>* track =
+          static_cast<MediaStreamTrackAtMessageData<T>*>(data);
+      track->track_ = track_list_->at(track->index_);
+      break;
+    }
+    default:
+      ASSERT(!"Not Implemented!");
+      break;
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamproxy.h b/talk/app/webrtc/mediastreamproxy.h
new file mode 100644
index 0000000..7d1068a
--- /dev/null
+++ b/talk/app/webrtc/mediastreamproxy.h
@@ -0,0 +1,111 @@
+/*
+ * 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_APP_WEBRTC_MEDIASTREAMPROXY_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMPROXY_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/mediastreamimpl.h"
+#include "talk/base/thread.h"
+
+namespace webrtc {
+using talk_base::scoped_refptr;
+
+// MediaStreamProxy is a proxy for the MediaStream interface. The purpose is
+// to make sure MediaStreamImpl is only accessed from the signaling thread.
+// It can be used as a proxy for both local and remote MediaStreams.
+class MediaStreamProxy : public LocalMediaStreamInterface,
+                         public talk_base::MessageHandler {
+ public:
+  static scoped_refptr<MediaStreamProxy> Create(
+      const std::string& label,
+      talk_base::Thread* signaling_thread);
+
+  static scoped_refptr<MediaStreamProxy> Create(
+      const std::string& label,
+      talk_base::Thread* signaling_thread,
+      LocalMediaStreamInterface* media_stream_impl);
+
+  // Implement LocalStream.
+  virtual bool AddTrack(AudioTrackInterface* track);
+  virtual bool AddTrack(VideoTrackInterface* track);
+
+  // Implement MediaStream.
+  virtual std::string label() const;
+  virtual AudioTracks* audio_tracks() {
+    return audio_tracks_;
+  }
+  virtual VideoTracks* video_tracks() {
+    return video_tracks_;
+  }
+  virtual ReadyState ready_state();
+  virtual void set_ready_state(ReadyState new_state);
+
+  // Implement Notifier
+  virtual void RegisterObserver(ObserverInterface* observer);
+  virtual void UnregisterObserver(ObserverInterface* observer);
+
+ protected:
+  MediaStreamProxy(const std::string& label,
+                   talk_base::Thread* signaling_thread,
+                   LocalMediaStreamInterface* media_stream_impl);
+
+  template <class T>
+  class MediaStreamTrackListProxy : public MediaStreamTrackListInterface<T>,
+                                    public talk_base::MessageHandler {
+   public:
+    explicit MediaStreamTrackListProxy(talk_base::Thread* signaling_thread);
+
+    void SetImplementation(MediaStreamTrackListInterface<T>* track_list);
+    virtual size_t count();
+    virtual T* at(size_t index);
+
+   private:
+    void Send(uint32 id, talk_base::MessageData* data) const;
+    void OnMessage(talk_base::Message* msg);
+
+    talk_base::scoped_refptr<MediaStreamTrackListInterface<T> > track_list_;
+    mutable talk_base::Thread* signaling_thread_;
+  };
+  typedef MediaStreamTrackListProxy<AudioTrackInterface> AudioTrackListProxy;
+  typedef MediaStreamTrackListProxy<VideoTrackInterface> VideoTrackListProxy;
+
+  void Send(uint32 id, talk_base::MessageData* data) const;
+  // Implement MessageHandler.
+  virtual void OnMessage(talk_base::Message* msg);
+
+  mutable talk_base::Thread* signaling_thread_;
+  scoped_refptr<LocalMediaStreamInterface> media_stream_impl_;
+  scoped_refptr<AudioTrackListProxy> audio_tracks_;
+  scoped_refptr<VideoTrackListProxy> video_tracks_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMPROXY_H_
diff --git a/talk/app/webrtc/mediastreamtrackproxy.cc b/talk/app/webrtc/mediastreamtrackproxy.cc
new file mode 100644
index 0000000..15b1d6d
--- /dev/null
+++ b/talk/app/webrtc/mediastreamtrackproxy.cc
@@ -0,0 +1,396 @@
+/*
+ * 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/app/webrtc/mediastreamtrackproxy.h"
+
+#include "talk/session/phone/videocapturer.h"
+
+namespace {
+
+enum {
+  MSG_REGISTER_OBSERVER = 1,
+  MSG_UNREGISTER_OBSERVER,
+  MSG_LABEL,
+  MSG_ENABLED,
+  MSG_SET_ENABLED,
+  MSG_STATE,
+  MSG_GET_AUDIODEVICE,
+  MSG_GET_VIDEODEVICE,
+  MSG_GET_VIDEORENDERER,
+  MSG_SET_VIDEORENDERER,
+};
+
+typedef talk_base::TypedMessageData<std::string*> LabelMessageData;
+typedef talk_base::TypedMessageData<webrtc::ObserverInterface*>
+    ObserverMessageData;
+typedef talk_base::TypedMessageData
+    <webrtc::MediaStreamTrackInterface::TrackState> TrackStateMessageData;
+typedef talk_base::TypedMessageData<bool> EnableMessageData;
+
+
+class AudioDeviceMessageData : public talk_base::MessageData {
+ public:
+  talk_base::scoped_refptr<webrtc::AudioDeviceModule> audio_device_;
+};
+
+class VideoDeviceMessageData : public talk_base::MessageData {
+ public:
+  cricket::VideoCapturer* video_device_;
+};
+
+class VideoRendererMessageData : public talk_base::MessageData {
+ public:
+  talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+      video_renderer_;
+};
+
+}  // namespace anonymous
+
+namespace webrtc {
+
+template <class T>
+MediaStreamTrackProxy<T>::MediaStreamTrackProxy(
+    talk_base::Thread* signaling_thread)
+    : signaling_thread_(signaling_thread) {
+}
+
+template <class T>
+void MediaStreamTrackProxy<T>::Init(MediaStreamTrackInterface* track) {
+  track_ = track;
+}
+
+template <class T>
+std::string MediaStreamTrackProxy<T>::kind() const {
+  return track_->kind();
+}
+
+template <class T>
+std::string MediaStreamTrackProxy<T>::label() const {
+  if (!signaling_thread_->IsCurrent()) {
+    std::string label;
+    LabelMessageData msg(&label);
+    Send(MSG_LABEL, &msg);
+    return label;
+  }
+  return track_->label();
+}
+
+template <class T>
+MediaStreamTrackInterface::TrackState MediaStreamTrackProxy<T>::state() const {
+  if (!signaling_thread_->IsCurrent()) {
+    TrackStateMessageData msg(MediaStreamTrackInterface::kInitializing);
+    Send(MSG_STATE, &msg);
+    return msg.data();
+  }
+  return track_->state();
+}
+
+template <class T>
+bool MediaStreamTrackProxy<T>::enabled() const {
+  if (!signaling_thread_->IsCurrent()) {
+    EnableMessageData msg(false);
+    Send(MSG_ENABLED, &msg);
+    return msg.data();
+  }
+  return track_->enabled();
+}
+
+template <class T>
+bool MediaStreamTrackProxy<T>::set_enabled(bool enable) {
+  if (!signaling_thread_->IsCurrent()) {
+    EnableMessageData msg(enable);
+    Send(MSG_SET_ENABLED, &msg);
+    return msg.data();
+  }
+  return track_->set_enabled(enable);
+}
+
+template <class T>
+bool MediaStreamTrackProxy<T>::set_state(
+    MediaStreamTrackInterface::TrackState new_state) {
+  if (!signaling_thread_->IsCurrent()) {
+    // State should only be allowed to be changed from the signaling thread.
+    ASSERT(!"Not Allowed!");
+    return false;
+  }
+  return track_->set_state(new_state);
+}
+
+template <class T>
+void MediaStreamTrackProxy<T>::RegisterObserver(ObserverInterface* observer) {
+  if (!signaling_thread_->IsCurrent()) {
+    ObserverMessageData msg(observer);
+    Send(MSG_REGISTER_OBSERVER, &msg);
+    return;
+  }
+  track_->RegisterObserver(observer);
+}
+
+template <class T>
+void MediaStreamTrackProxy<T>::UnregisterObserver(ObserverInterface* observer) {
+  if (!signaling_thread_->IsCurrent()) {
+    ObserverMessageData msg(observer);
+    Send(MSG_UNREGISTER_OBSERVER, &msg);
+    return;
+  }
+  track_->UnregisterObserver(observer);
+}
+
+template <class T>
+void MediaStreamTrackProxy<T>::Send(uint32 id,
+                                    talk_base::MessageData* data) const {
+  signaling_thread_->Send(const_cast<MediaStreamTrackProxy<T>*>(this), id,
+                          data);
+}
+
+template <class T>
+bool MediaStreamTrackProxy<T>::HandleMessage(talk_base::Message* msg) {
+  talk_base::MessageData* data = msg->pdata;
+  switch (msg->message_id) {
+    case MSG_REGISTER_OBSERVER: {
+      ObserverMessageData* observer = static_cast<ObserverMessageData*>(data);
+      track_->RegisterObserver(observer->data());
+      return true;
+      break;
+    }
+    case MSG_UNREGISTER_OBSERVER: {
+      ObserverMessageData* observer = static_cast<ObserverMessageData*>(data);
+      track_->UnregisterObserver(observer->data());
+      return true;
+      break;
+    }
+    case MSG_LABEL: {
+      LabelMessageData* label = static_cast<LabelMessageData*>(data);
+      *(label->data()) = track_->label();
+      return true;
+    }
+    case MSG_SET_ENABLED: {
+      EnableMessageData* enabled = static_cast<EnableMessageData*>(data);
+      enabled->data() = track_->set_enabled(enabled->data());
+      return true;
+      break;
+    }
+    case MSG_ENABLED: {
+      EnableMessageData* enabled = static_cast<EnableMessageData*>(data);
+      enabled->data() = track_->enabled();
+      return true;
+      break;
+    }
+    case MSG_STATE: {
+      TrackStateMessageData* state = static_cast<TrackStateMessageData*>(data);
+      state->data() = track_->state();
+      return true;
+      break;
+    }
+    default:
+      return false;
+  }
+}
+
+AudioTrackProxy::AudioTrackProxy(const std::string& label,
+                                 talk_base::Thread* signaling_thread)
+    : MediaStreamTrackProxy<LocalAudioTrackInterface>(signaling_thread),
+      audio_track_(AudioTrack::CreateRemote(label)) {
+  Init(audio_track_);
+}
+
+AudioTrackProxy::AudioTrackProxy(const std::string& label,
+                                 AudioDeviceModule* audio_device,
+                                 talk_base::Thread* signaling_thread)
+    : MediaStreamTrackProxy<LocalAudioTrackInterface>(signaling_thread),
+      audio_track_(AudioTrack::CreateLocal(label, audio_device)) {
+  Init(audio_track_);
+}
+
+AudioTrackProxy::AudioTrackProxy(LocalAudioTrackInterface* implementation,
+                                 talk_base::Thread* signaling_thread)
+    : MediaStreamTrackProxy<LocalAudioTrackInterface>(signaling_thread),
+      audio_track_(implementation) {
+  Init(audio_track_);
+}
+
+talk_base::scoped_refptr<AudioTrackInterface> AudioTrackProxy::CreateRemote(
+    const std::string& label,
+    talk_base::Thread* signaling_thread) {
+  ASSERT(signaling_thread != NULL);
+  talk_base::RefCountedObject<AudioTrackProxy>* track =
+      new talk_base::RefCountedObject<AudioTrackProxy>(label, signaling_thread);
+  return track;
+}
+
+talk_base::scoped_refptr<LocalAudioTrackInterface> AudioTrackProxy::CreateLocal(
+    const std::string& label,
+    AudioDeviceModule* audio_device,
+    talk_base::Thread* signaling_thread) {
+  ASSERT(signaling_thread != NULL);
+  talk_base::RefCountedObject<AudioTrackProxy>* track =
+      new talk_base::RefCountedObject<AudioTrackProxy>(label,
+                                               audio_device,
+                                               signaling_thread);
+  return track;
+}
+
+talk_base::scoped_refptr<LocalAudioTrackInterface> AudioTrackProxy::CreateLocal(
+    LocalAudioTrackInterface* implementation,
+    talk_base::Thread* signaling_thread) {
+  ASSERT(signaling_thread != NULL);
+  talk_base::RefCountedObject<AudioTrackProxy>* track =
+      new talk_base::RefCountedObject<AudioTrackProxy>(implementation,
+                                               signaling_thread);
+  return track;
+}
+
+AudioDeviceModule* AudioTrackProxy::GetAudioDevice() {
+  if (!signaling_thread_->IsCurrent()) {
+    AudioDeviceMessageData msg;
+    Send(MSG_GET_AUDIODEVICE, &msg);
+    return msg.audio_device_;
+  }
+  return audio_track_->GetAudioDevice();
+}
+
+void AudioTrackProxy::OnMessage(talk_base::Message* msg) {
+  if (!MediaStreamTrackProxy<LocalAudioTrackInterface>::HandleMessage(msg)) {
+    if (msg->message_id == MSG_GET_AUDIODEVICE) {
+      AudioDeviceMessageData* audio_device =
+          static_cast<AudioDeviceMessageData*>(msg->pdata);
+      audio_device->audio_device_ = audio_track_->GetAudioDevice();
+    } else {
+      ASSERT(!"Not Implemented!");
+    }
+  }
+}
+
+VideoTrackProxy::VideoTrackProxy(const std::string& label,
+                                 talk_base::Thread* signaling_thread)
+    : MediaStreamTrackProxy<LocalVideoTrackInterface>(signaling_thread),
+      video_track_(VideoTrack::CreateRemote(label)) {
+  Init(video_track_);
+}
+
+VideoTrackProxy::VideoTrackProxy(const std::string& label,
+                                 cricket::VideoCapturer* video_device,
+                                 talk_base::Thread* signaling_thread)
+    : MediaStreamTrackProxy<LocalVideoTrackInterface>(signaling_thread),
+      video_track_(VideoTrack::CreateLocal(label, video_device)) {
+  Init(video_track_);
+}
+
+VideoTrackProxy::VideoTrackProxy(LocalVideoTrackInterface* implementation,
+                                 talk_base::Thread* signaling_thread)
+    : MediaStreamTrackProxy<LocalVideoTrackInterface>(signaling_thread),
+      video_track_(implementation) {
+  Init(video_track_);
+}
+
+talk_base::scoped_refptr<VideoTrackInterface> VideoTrackProxy::CreateRemote(
+    const std::string& label,
+    talk_base::Thread* signaling_thread) {
+  ASSERT(signaling_thread != NULL);
+  talk_base::RefCountedObject<VideoTrackProxy>* track =
+      new talk_base::RefCountedObject<VideoTrackProxy>(label, signaling_thread);
+  return track;
+}
+
+talk_base::scoped_refptr<LocalVideoTrackInterface> VideoTrackProxy::CreateLocal(
+    const std::string& label,
+    cricket::VideoCapturer* video_device,
+    talk_base::Thread* signaling_thread) {
+  ASSERT(signaling_thread != NULL);
+  talk_base::RefCountedObject<VideoTrackProxy>* track =
+      new talk_base::RefCountedObject<VideoTrackProxy>(label, video_device,
+                                                       signaling_thread);
+  return track;
+}
+
+talk_base::scoped_refptr<LocalVideoTrackInterface> VideoTrackProxy::CreateLocal(
+    LocalVideoTrackInterface* implementation,
+    talk_base::Thread* signaling_thread) {
+  ASSERT(signaling_thread != NULL);
+  talk_base::RefCountedObject<VideoTrackProxy>* track =
+      new talk_base::RefCountedObject<VideoTrackProxy>(implementation,
+                                                       signaling_thread);
+  return track;
+}
+
+cricket::VideoCapturer* VideoTrackProxy::GetVideoCapture() {
+  if (!signaling_thread_->IsCurrent()) {
+    VideoDeviceMessageData msg;
+    Send(MSG_GET_VIDEODEVICE, &msg);
+    return msg.video_device_;
+  }
+  return video_track_->GetVideoCapture();
+}
+
+void VideoTrackProxy::SetRenderer(VideoRendererWrapperInterface* renderer) {
+  if (!signaling_thread_->IsCurrent()) {
+    VideoRendererMessageData msg;
+    msg.video_renderer_ = renderer;
+    Send(MSG_SET_VIDEORENDERER, &msg);
+    return;
+  }
+  return video_track_->SetRenderer(renderer);
+}
+
+VideoRendererWrapperInterface* VideoTrackProxy::GetRenderer() {
+  if (!signaling_thread_->IsCurrent()) {
+    VideoRendererMessageData msg;
+    Send(MSG_GET_VIDEORENDERER, &msg);
+    return msg.video_renderer_;
+  }
+  return video_track_->GetRenderer();
+}
+
+void VideoTrackProxy::OnMessage(talk_base::Message* msg) {
+  if (!MediaStreamTrackProxy<LocalVideoTrackInterface>::HandleMessage(msg)) {
+    switch (msg->message_id) {
+      case  MSG_GET_VIDEODEVICE: {
+        VideoDeviceMessageData* video_device =
+            static_cast<VideoDeviceMessageData*>(msg->pdata);
+        video_device->video_device_ = video_track_->GetVideoCapture();
+        break;
+      }
+      case MSG_GET_VIDEORENDERER: {
+        VideoRendererMessageData* video_renderer =
+            static_cast<VideoRendererMessageData*>(msg->pdata);
+        video_renderer->video_renderer_ = video_track_->GetRenderer();
+        break;
+      }
+      case MSG_SET_VIDEORENDERER: {
+        VideoRendererMessageData* video_renderer =
+            static_cast<VideoRendererMessageData*>(msg->pdata);
+        video_track_->SetRenderer(video_renderer->video_renderer_.get());
+        break;
+      }
+    default:
+      ASSERT(!"Not Implemented!");
+      break;
+    }
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamtrackproxy.h b/talk/app/webrtc/mediastreamtrackproxy.h
new file mode 100644
index 0000000..590d6ab
--- /dev/null
+++ b/talk/app/webrtc/mediastreamtrackproxy.h
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+// This file includes proxy classes for tracks. The purpose is
+// to make sure tracks are only accessed from the signaling thread.
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAMTRACKPROXY_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMTRACKPROXY_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/audiotrackimpl.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/videotrackimpl.h"
+#include "talk/base/thread.h"
+
+namespace cricket {
+
+class VideoCapturer;
+
+}  // namespace cricket
+
+namespace webrtc {
+
+template <class T>
+class MediaStreamTrackProxy : public T,
+                              talk_base::MessageHandler {
+ public:
+  void Init(MediaStreamTrackInterface* track);
+  // Implement MediaStreamTrack.
+
+  virtual std::string kind() const;
+  virtual std::string label() const;
+  virtual bool enabled() const;
+  virtual MediaStreamTrackInterface::TrackState state() const;
+  virtual bool set_enabled(bool enable);
+  virtual bool set_state(MediaStreamTrackInterface::TrackState new_state);
+
+  // Implement Notifier
+  virtual void RegisterObserver(ObserverInterface* observer);
+  virtual void UnregisterObserver(ObserverInterface* observer);
+
+ protected:
+  explicit MediaStreamTrackProxy(talk_base::Thread* signaling_thread);
+
+  void Send(uint32 id, talk_base::MessageData* data) const;
+  // Returns true if the message is handled.
+  bool HandleMessage(talk_base::Message* msg);
+
+  mutable talk_base::Thread* signaling_thread_;
+  MediaStreamTrackInterface* track_;
+};
+
+// AudioTrackProxy is a proxy for the AudioTrackInterface. The purpose is
+// to make sure AudioTrack is only accessed from the signaling thread.
+// It can be used as a proxy for both local and remote audio tracks.
+class AudioTrackProxy : public MediaStreamTrackProxy<LocalAudioTrackInterface> {
+ public:
+  static talk_base::scoped_refptr<AudioTrackInterface> CreateRemote(
+      const std::string& label,
+      talk_base::Thread* signaling_thread);
+  static talk_base::scoped_refptr<LocalAudioTrackInterface> CreateLocal(
+      const std::string& label,
+      AudioDeviceModule* audio_device,
+      talk_base::Thread* signaling_thread);
+  static talk_base::scoped_refptr<LocalAudioTrackInterface> CreateLocal(
+      LocalAudioTrackInterface* implementation,
+      talk_base::Thread* signaling_thread);
+
+  virtual AudioDeviceModule* GetAudioDevice();
+
+ protected:
+  AudioTrackProxy(const std::string& label,
+                  talk_base::Thread* signaling_thread);
+  AudioTrackProxy(const std::string& label,
+                  AudioDeviceModule* audio_device,
+                  talk_base::Thread* signaling_thread);
+  AudioTrackProxy(LocalAudioTrackInterface* implementation,
+                  talk_base::Thread* signaling_thread);
+  // Implement MessageHandler
+  virtual void OnMessage(talk_base::Message* msg);
+
+  talk_base::scoped_refptr<LocalAudioTrackInterface> audio_track_;
+};
+
+// VideoTrackProxy is a proxy for the VideoTrackInterface and
+// LocalVideoTrackInterface. The purpose is
+// to make sure VideoTrack is only accessed from the signaling thread.
+// It can be used as a proxy for both local and remote video tracks.
+class VideoTrackProxy : public MediaStreamTrackProxy<LocalVideoTrackInterface> {
+ public:
+  static talk_base::scoped_refptr<VideoTrackInterface> CreateRemote(
+      const std::string& label,
+      talk_base::Thread* signaling_thread);
+  static talk_base::scoped_refptr<LocalVideoTrackInterface> CreateLocal(
+      const std::string& label,
+      cricket::VideoCapturer* video_device,
+      talk_base::Thread* signaling_thread);
+  static talk_base::scoped_refptr<LocalVideoTrackInterface> CreateLocal(
+      LocalVideoTrackInterface* implementation,
+      talk_base::Thread* signaling_thread);
+
+  virtual cricket::VideoCapturer* GetVideoCapture();
+  virtual void SetRenderer(VideoRendererWrapperInterface* renderer);
+  VideoRendererWrapperInterface* GetRenderer();
+
+ protected:
+  VideoTrackProxy(const std::string& label,
+                  talk_base::Thread* signaling_thread);
+  VideoTrackProxy(const std::string& label,
+                  cricket::VideoCapturer* video_device,
+                  talk_base::Thread* signaling_thread);
+  VideoTrackProxy(LocalVideoTrackInterface* implementation,
+                  talk_base::Thread* signaling_thread);
+
+  // Implement MessageHandler
+  virtual void OnMessage(talk_base::Message* msg);
+
+  talk_base::scoped_refptr<LocalVideoTrackInterface> video_track_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMTRACKPROXY_H_
diff --git a/talk/app/webrtc/mediatrackimpl.h b/talk/app/webrtc/mediatrackimpl.h
new file mode 100644
index 0000000..e60801d
--- /dev/null
+++ b/talk/app/webrtc/mediatrackimpl.h
@@ -0,0 +1,81 @@
+/*
+ * 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_APP_WEBRTC_MEDIATRACKIMPL_H_
+#define TALK_APP_WEBRTC_MEDIATRACKIMPL_H_
+
+#include <string>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/notifierimpl.h"
+
+namespace webrtc {
+
+// MediaTrack implements the interface common to AudioTrackInterface and
+// VideoTrackInterface.
+template <typename T>
+class MediaStreamTrack : public Notifier<T> {
+ public:
+  typedef typename T::TrackState TypedTrackState;
+
+  virtual std::string label() const { return label_; }
+  virtual MediaStreamTrackInterface::TrackState state() const {
+    return state_;
+  }
+  virtual bool enabled() const { return enabled_; }
+  virtual bool set_enabled(bool enable) {
+    bool fire_on_change = (enable != enabled_);
+    enabled_ = enable;
+    if (fire_on_change) {
+      Notifier<T>::FireOnChanged();
+    }
+    return fire_on_change;
+  }
+  virtual bool set_state(MediaStreamTrackInterface::TrackState new_state) {
+    bool fire_on_change = (state_ != new_state);
+    state_ = new_state;
+    if (fire_on_change)
+      Notifier<T>::FireOnChanged();
+    return true;
+  }
+
+ protected:
+  explicit MediaStreamTrack(const std::string& label)
+      : enabled_(true),
+        label_(label),
+        state_(MediaStreamTrackInterface::kInitializing) {
+  }
+
+ private:
+  bool enabled_;
+  std::string label_;
+  MediaStreamTrackInterface::TrackState state_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIATRACKIMPL_H_
diff --git a/talk/app/webrtc/notifierimpl.h b/talk/app/webrtc/notifierimpl.h
new file mode 100644
index 0000000..bd7c183
--- /dev/null
+++ b/talk/app/webrtc/notifierimpl.h
@@ -0,0 +1,73 @@
+/*
+ * 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_APP_WEBRTC_NOTIFIERIMPL_H_
+#define TALK_APP_WEBRTC_NOTIFIERIMPL_H_
+
+#include <list>
+
+#include "talk/base/common.h"
+#include "talk/app/webrtc/mediastream.h"
+
+namespace webrtc {
+
+// Implement a template version of a notifier.
+template <class T>
+class Notifier : public T {
+ public:
+  Notifier() {
+  }
+
+  virtual void RegisterObserver(ObserverInterface* observer) {
+    ASSERT(observer != NULL);
+    observers_.push_back(observer);
+  }
+
+  virtual void UnregisterObserver(ObserverInterface* observer) {
+    for (std::list<ObserverInterface*>::iterator it = observers_.begin();
+         it != observers_.end(); it++) {
+      if (*it == observer) {
+        observers_.erase(it);
+        break;
+      }
+    }
+  }
+
+  void FireOnChanged() {
+    for (std::list<ObserverInterface*>::iterator it = observers_.begin();
+         it != observers_.end(); ++it) {
+      (*it)-> OnChanged();
+    }
+  }
+
+ protected:
+  std::list<ObserverInterface*> observers_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_NOTIFIERIMPL_H_
diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h
index 62c2504..24a1cc4 100644
--- a/talk/app/webrtc/peerconnection.h
+++ b/talk/app/webrtc/peerconnection.h
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * 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:
@@ -25,107 +25,253 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+// This file contains the PeerConnection interface as defined in
+// http://dev.w3.org/2011/webrtc/editor/webrtc.html#peer-to-peer-connections.
+// Applications must use this interface to implement peerconnection.
+// PeerConnectionFactory class provides factory methods to create
+// peerconnection, mediastream and media tracks objects.
+//
+// The Following steps are needed to setup a typical call.
+// 1. Create a PeerConnectionFactoryInterface. Check constructors for more
+// information about input parameters.
+// 2. Create a PeerConnection object. Provide a configuration string which
+// points either to stun or turn server to generate ICE candidates and provide
+// an object that implements the PeerConnectionObserver interface.
+// Now PeerConnection will startcollecting ICE candidates.
+// 3. Create local MediaStream and MediaTracks using the PeerConnectionFactory
+// and add it to PeerConnection by calling AddStream.
+// 4. Once all mediastreams are added to peerconnection, call
+// CommitStreamChanges. Now PeerConnection starts generating an offer based on
+// the local mediastreams.
+// 5. When PeerConnection have generated the ICE candidates it will call the
+// observer OnSignalingMessage callback with the initial offer.
+// 6. When an Answer from peer received it must be supplied to the
+// PeerConnection by calling ProcessSignalingMessage.
+// At this point PeerConnection knows remote capabilities and ICE candidates.
+// Media will start flowing to the remote peer.
+
+// The Receiver of a call can decide to accept or reject the call.
+// This decision will be taken by the application not peerconnection.
+// If application decides to accept the call
+// 1. Create PeerConnectionFactoryInterface if it doesn't exist.
+// 2. Create new PeerConnection
+// 3. Provide the remote offer to the new PeerConnection object by calling
+// ProcessSignalingMessage.
+// 4. PeerConnection will call the observer function OnAddStream with remote
+// MediaStream and tracks information.
+// 5. PeerConnection will call the observer function OnSignalingMessage with
+// local ICE candidates in a answer message.
+// 6. Application can add it's own MediaStreams by calling AddStream.
+// When all streams have been added the application must call
+// CommitStreamChanges. Streams can be added at any time after the
+// PeerConnection object have been created.
+
 #ifndef TALK_APP_WEBRTC_PEERCONNECTION_H_
 #define TALK_APP_WEBRTC_PEERCONNECTION_H_
 
-// TODO - Add a factory class or some kind of PeerConnection manager
-// to support multiple PeerConnection object instantiation. This class will
-// create ChannelManager object and pass it to PeerConnection object. Otherwise
-// each PeerConnection object will have its own ChannelManager hence MediaEngine
-// and VoiceEngine/VideoEngine.
-
 #include <string>
+#include <vector>
 
-namespace cricket {
-class VideoRenderer;
-}
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/base/socketaddress.h"
 
 namespace talk_base {
 class Thread;
 }
 
-namespace webrtc {
+namespace cricket {
+class PortAllocator;
+}
 
+namespace webrtc {
+class VideoCaptureModule;
+
+// MediaStream container interface.
+class StreamCollectionInterface : public talk_base::RefCountInterface {
+ public:
+  virtual size_t count() = 0;
+  virtual MediaStreamInterface* at(size_t index) = 0;
+  virtual MediaStreamInterface* find(const std::string& label) = 0;
+ protected:
+  // Dtor protected as objects shouldn't be deleted via this interface.
+  ~StreamCollectionInterface() {}
+};
+
+// PeerConnection callback interface. Application should implement these
+// methods.
 class PeerConnectionObserver {
  public:
-  // serialized signaling message
+  enum StateType {
+    kReadyState,
+    kIceState,
+    kSdpState,
+  };
+
+  virtual void OnError() = 0;
+
+  virtual void OnMessage(const std::string& msg) = 0;
+
+  // Serialized signaling message
   virtual void OnSignalingMessage(const std::string& msg) = 0;
 
-  // Triggered when a remote peer accepts a media connection.
-  virtual void OnAddStream(const std::string& stream_id, bool video) = 0;
+  // Triggered when ReadyState, SdpState or IceState have changed.
+  virtual void OnStateChange(StateType state_changed) = 0;
 
-  // Triggered when a remote peer closes a media stream.
-  virtual void OnRemoveStream(const std::string& stream_id, bool video) = 0;
+  // Triggered when media is received on a new stream from remote peer.
+  virtual void OnAddStream(MediaStreamInterface* stream) = 0;
+
+  // Triggered when a remote peer close a stream.
+  virtual void OnRemoveStream(MediaStreamInterface* stream) = 0;
 
  protected:
   // Dtor protected as objects shouldn't be deleted via this interface.
   ~PeerConnectionObserver() {}
 };
 
-class PeerConnection {
+
+class PeerConnectionInterface : public talk_base::RefCountInterface {
  public:
   enum ReadyState {
-    NEW = 0,
-    NEGOTIATING,
-    ACTIVE,
-    CLOSED,
+    kNew,
+    kNegotiating,
+    kActive,
+    kClosing,
+    kClosed,
   };
 
-  virtual ~PeerConnection() {}
+  enum SdpState {
+    kSdpNew,
+    kSdpIdle,
+    kSdpWaiting,
+  };
 
-  // Register a listener
-  virtual void RegisterObserver(PeerConnectionObserver* observer) = 0;
+  // Process a signaling message using the ROAP protocol.
+  virtual void ProcessSignalingMessage(const std::string& msg) = 0;
 
-  // SignalingMessage in json format
-  virtual bool SignalingMessage(const std::string& msg) = 0;
+  // Sends the msg over a data stream.
+  virtual bool Send(const std::string& msg) = 0;
 
-  // Asynchronously adds a local stream device to the peer
-  // connection.
-  virtual bool AddStream(const std::string& stream_id, bool video) = 0;
+  // Accessor methods to active local streams.
+  virtual talk_base::scoped_refptr<StreamCollectionInterface>
+      local_streams() = 0;
 
-  // Asynchronously removes a local stream device from the peer
-  // connection. The operation is complete when
-  // PeerConnectionObserver::OnRemoveStream is called.
-  virtual bool RemoveStream(const std::string& stream_id) = 0;
+  // Accessor methods to remote streams.
+  virtual talk_base::scoped_refptr<StreamCollectionInterface>
+      remote_streams() = 0;
 
-  // Info the peerconnection that it is time to return the signaling
-  // information. The operation is complete when
-  // PeerConnectionObserver::OnSignalingMessage is called.
-  virtual bool Connect() = 0;
+  // Add a new local stream.
+  // This function does not trigger any changes to the stream until
+  // CommitStreamChanges is called.
+  virtual void AddStream(LocalMediaStreamInterface* stream) = 0;
 
-  // Remove all the streams and tear down the session.
-  // After the Close() is called, the OnSignalingMessage will be invoked
-  // asynchronously. And before OnSignalingMessage is called,
-  // OnRemoveStream will be called for each stream that was active.
-  // TODO: Add an event such as onclose, or onreadystatechanged
-  // when the readystate reaches the closed state (no more streams in the
-  // peerconnection object.
-  virtual bool Close() = 0;
+  // Remove a local stream and stop sending it.
+  // This function does not trigger any changes to the stream until
+  // CommitStreamChanges is called.
+  virtual void RemoveStream(LocalMediaStreamInterface* stream) = 0;
 
-  // Set the audio input & output devices based on the given device name.
-  // An empty device name means to use the default audio device.
-  virtual bool SetAudioDevice(const std::string& wave_in_device,
-                              const std::string& wave_out_device,
-                              int opts) = 0;
+  // Commit Stream changes. This will start sending media on new streams
+  // and stop sending media on removed streams.
+  virtual void CommitStreamChanges() = 0;
 
-  // Set the video renderer for the camera preview.
-  virtual bool SetLocalVideoRenderer(cricket::VideoRenderer* renderer) = 0;
+  // Close the current session. This will trigger a Shutdown message
+  // being sent and the readiness state change to Closing.
+  // After calling this function no changes can be made to the sending streams.
+  virtual void Close() = 0;
 
-  // Set the video renderer for the specified stream.
-  virtual bool SetVideoRenderer(const std::string& stream_id,
-                                cricket::VideoRenderer* renderer) = 0;
+  // Returns the current ReadyState.
+  virtual ReadyState ready_state() = 0;
 
-  // Set video capture device
-  // For Chromium the cam_device should use the capture session id.
-  // For standalone app, cam_device is the camera name. It will try to
-  // set the default capture device when cam_device is "".
-  virtual bool SetVideoCapture(const std::string& cam_device) = 0;
+  // Returns the current SdpState.
+  virtual SdpState sdp_state() = 0;
 
-  // Returns the state of the PeerConnection object.  See the ReadyState
-  // enum for valid values.
-  virtual ReadyState GetReadyState() = 0;
+ protected:
+  // Dtor protected as objects shouldn't be deleted via this interface.
+  ~PeerConnectionInterface() {}
 };
 
+// Helper function to create a new instance of cricket::VideoCapturer
+// from VideoCaptureModule.
+// TODO: This function should be removed once chrome implement video
+// capture as the cricket::VideoCapturer.
+cricket::VideoCapturer* CreateVideoCapturer(VideoCaptureModule* vcm);
+
+// Factory class used for creating cricket::PortAllocator that is used
+// for ICE negotiation.
+class PortAllocatorFactoryInterface : public talk_base::RefCountInterface {
+ public:
+  struct StunConfiguration {
+    StunConfiguration(const std::string& address, int port)
+        : server(address, port) {}
+    // STUN server address and port.
+    talk_base::SocketAddress server;
+  };
+
+  struct TurnConfiguration {
+    TurnConfiguration(const std::string& address,
+                      int port,
+                      const std::string& username,
+                      const std::string& password)
+        : server(address, port),
+          username(username),
+          password(password) {}
+    talk_base::SocketAddress server;
+    std::string username;
+    std::string password;
+  };
+
+  virtual cricket::PortAllocator* CreatePortAllocator(
+      const std::vector<StunConfiguration>& stun_servers,
+      const std::vector<TurnConfiguration>& turn_configurations) = 0;
+
+ protected:
+  PortAllocatorFactoryInterface() {}
+  ~PortAllocatorFactoryInterface() {}
+};
+
+// PeerConnectionFactoryInterface is the factory interface use for creating
+// PeerConnection, MediaStream and media tracks.
+// PeerConnectionFactoryInterface will create required libjingle threads,
+// socket and network manager factory classes for networking.
+// If application decides to provide its own implementation of these classes
+// it should use alternate create method which accepts a threads and a
+// PortAllocatorFactoryInterface as input.
+class PeerConnectionFactoryInterface : public talk_base::RefCountInterface {
+ public:
+  virtual talk_base::scoped_refptr<PeerConnectionInterface>
+      CreatePeerConnection(const std::string& config,
+                           PeerConnectionObserver* observer) = 0;
+
+  virtual talk_base::scoped_refptr<LocalMediaStreamInterface>
+      CreateLocalMediaStream(const std::string& label) = 0;
+
+  virtual talk_base::scoped_refptr<LocalVideoTrackInterface>
+      CreateLocalVideoTrack(const std::string& label,
+                            cricket::VideoCapturer* video_device) = 0;
+
+  virtual talk_base::scoped_refptr<LocalAudioTrackInterface>
+      CreateLocalAudioTrack(const std::string& label,
+                            AudioDeviceModule* audio_device) = 0;
+
+ protected:
+  // Dtor and ctor protected as objects shouldn't be created or deleted via
+  // this interface.
+  PeerConnectionFactoryInterface() {}
+  ~PeerConnectionFactoryInterface() {} // NOLINT
+};
+
+// Create a new instance of PeerConnectionFactoryInterface.
+talk_base::scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory();
+
+// Create a new instance of PeerConnectionFactoryInterface.
+// Ownership of the arguments are not transfered to this object and must
+// remain in scope for the lifetime of the PeerConnectionFactoryInterface.
+talk_base::scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory(talk_base::Thread* worker_thread,
+                            talk_base::Thread* signaling_thread,
+                            PortAllocatorFactoryInterface* factory,
+                            AudioDeviceModule* default_adm);
+
 }  // namespace webrtc
 
 #endif  // TALK_APP_WEBRTC_PEERCONNECTION_H_
diff --git a/talk/app/webrtc/peerconnection_unittest.cc b/talk/app/webrtc/peerconnection_unittest.cc
new file mode 100644
index 0000000..2caa7c6
--- /dev/null
+++ b/talk/app/webrtc/peerconnection_unittest.cc
@@ -0,0 +1,351 @@
+/*
+ * 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 <stdio.h>
+
+#include <list>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtc/test/fakevideocapturemodule.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/fakevideorenderer.h"
+#include "talk/session/phone/videorenderer.h"
+
+void GetAllVideoTracks(webrtc::MediaStreamInterface* media_stream,
+                       std::list<webrtc::VideoTrackInterface*>* video_tracks) {
+  webrtc::VideoTracks* track_list = media_stream->video_tracks();
+  for (size_t i = 0; i < track_list->count(); ++i) {
+    webrtc::VideoTrackInterface* track = track_list->at(i);
+    video_tracks->push_back(track);
+  }
+}
+
+class SignalingMessageReceiver {
+ public:
+  virtual void ReceiveMessage(const std::string& msg) = 0;
+
+  virtual int num_rendered_frames() = 0;
+
+  // Makes it possible for the remote side to decide when to start capturing.
+  // This makes it possible to wait with capturing until a renderer has been
+  // added.
+  virtual void StartCapturing() = 0;
+
+ protected:
+  SignalingMessageReceiver() {}
+  virtual ~SignalingMessageReceiver() {}
+};
+
+class PeerConnectionP2PTestClient
+    : public webrtc::PeerConnectionObserver,
+      public SignalingMessageReceiver {
+ public:
+  static PeerConnectionP2PTestClient* CreateClient(int id) {
+    PeerConnectionP2PTestClient* client = new PeerConnectionP2PTestClient(id);
+    if (!client->Init()) {
+      delete client;
+      return NULL;
+    }
+    return client;
+  }
+
+  ~PeerConnectionP2PTestClient() {
+  }
+
+  void StartSession() {
+    if (video_track_.get() != NULL) {
+      // Tracks have already been set up.
+      return;
+    }
+    // TODO: the default audio device module is used regardless of
+    // the second parameter to the CreateLocalAudioTrack(..) call. Maybe remove
+    // the second parameter from the API altogether?
+    talk_base::scoped_refptr<webrtc::LocalAudioTrackInterface> audio_track(
+        peer_connection_factory_->CreateLocalAudioTrack("audio_track", NULL));
+
+    CreateLocalVideoTrack();
+
+    talk_base::scoped_refptr<webrtc::LocalMediaStreamInterface> stream =
+        peer_connection_factory_->CreateLocalMediaStream("stream_label");
+
+    stream->AddTrack(audio_track);
+    stream->AddTrack(video_track_);
+
+    peer_connection_->AddStream(stream);
+    peer_connection_->CommitStreamChanges();
+  }
+
+  void StartCapturing() {
+    if (fake_video_capture_module_ != NULL) {
+      fake_video_capture_module_->StartCapturing();
+    }
+  }
+
+  bool SessionActive() {
+    return peer_connection_->ready_state() ==
+        webrtc::PeerConnectionInterface::kActive;
+  }
+
+  void StopSession() {
+    if (fake_video_capture_module_ != NULL) {
+      fake_video_capture_module_->StopCapturing();
+    }
+    // TODO: investigate why calling Close() triggers a crash when
+    // deleting the PeerConnection.
+    // peer_connection_->Close();
+  }
+
+  void set_signaling_message_receiver(
+      SignalingMessageReceiver* signaling_message_receiver) {
+    signaling_message_receiver_ = signaling_message_receiver;
+  }
+
+  bool FramesReceivedCheck(int number_of_frames) {
+    if (number_of_frames > signaling_message_receiver_->num_rendered_frames()) {
+      return false;
+    }
+    else {
+      EXPECT_LT(number_of_frames, fake_video_capture_module_->sent_frames());
+    }
+    return true;
+  }
+
+  // SignalingMessageReceiver callback.
+  virtual void ReceiveMessage(const std::string& msg) {
+    peer_connection_->ProcessSignalingMessage(msg);
+  }
+
+  virtual int num_rendered_frames() {
+    if (fake_video_renderer_ == NULL) {
+      return -1;
+    }
+    return fake_video_renderer_->num_rendered_frames();
+  }
+
+  // PeerConnectionObserver callbacks.
+  virtual void OnError() {}
+  virtual void OnMessage(const std::string&) {}
+  virtual void OnSignalingMessage(const std::string& msg)  {
+    if (signaling_message_receiver_ == NULL) {
+      // Remote party may be deleted.
+      return;
+    }
+    signaling_message_receiver_->ReceiveMessage(msg);
+  }
+  virtual void OnStateChange(StateType /*state_changed*/) {}
+  virtual void OnAddStream(webrtc::MediaStreamInterface* media_stream) {
+    std::list<webrtc::VideoTrackInterface*> video_tracks;
+    GetAllVideoTracks(media_stream, &video_tracks);
+    int track_id = 0;
+    // Currently only one video track is supported.
+    // TODO: enable multiple video tracks.
+    EXPECT_EQ(1u, video_tracks.size());
+    for (std::list<webrtc::VideoTrackInterface*>::iterator iter =
+             video_tracks.begin();
+         iter != video_tracks.end();
+         ++iter) {
+      fake_video_renderer_ = new cricket::FakeVideoRenderer();
+      video_renderer_wrapper_ = webrtc::CreateVideoRenderer(
+          fake_video_renderer_);
+      (*iter)->SetRenderer(video_renderer_wrapper_);
+      track_id++;
+    }
+    // The video renderer has been added. Tell the far end to start capturing
+    // frames. That way the number of captured frames should be equal to number
+    // of rendered frames.
+    if (signaling_message_receiver_ != NULL) {
+      signaling_message_receiver_->StartCapturing();
+      return;
+    }
+  }
+  virtual void OnRemoveStream(webrtc::MediaStreamInterface* /*media_stream*/) {
+  }
+
+ private:
+  explicit PeerConnectionP2PTestClient(int id)
+      : id_(id),
+        fake_video_capture_module_(NULL),
+        fake_video_renderer_(NULL),
+        signaling_message_receiver_(NULL) {
+  }
+
+  bool Init() {
+    EXPECT_TRUE(peer_connection_.get() == NULL);
+    EXPECT_TRUE(peer_connection_factory_.get() == NULL);
+    peer_connection_factory_ = webrtc::CreatePeerConnectionFactory();
+    if (peer_connection_factory_.get() == NULL) {
+      return false;
+    }
+
+    const std::string server_configuration = "STUN stun.l.google.com:19302";
+    peer_connection_ = peer_connection_factory_->CreatePeerConnection(
+        server_configuration, this);
+    return peer_connection_.get() != NULL;
+  }
+
+  void GenerateRecordingFileName(int track, std::string* file_name) {
+    if (file_name == NULL) {
+      return;
+    }
+    std::stringstream file_name_stream;
+    file_name_stream << "p2p_test_client_" << id_ << "_videotrack_" << track <<
+        ".yuv";
+    file_name->clear();
+    *file_name = file_name_stream.str();
+  }
+
+  void CreateLocalVideoTrack() {
+    fake_video_capture_module_ = FakeVideoCaptureModule::Create(
+        talk_base::Thread::Current());
+    // TODO: Use FakeVideoCapturer instead of FakeVideoCaptureModule.
+    video_track_ = peer_connection_factory_->CreateLocalVideoTrack(
+        "video_track", CreateVideoCapturer(fake_video_capture_module_));
+  }
+
+  int id_;
+  talk_base::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
+  talk_base::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
+      peer_connection_factory_;
+
+  // Owns and ensures that fake_video_capture_module_ is available as long as
+  // this class exists.  It also ensures destruction of the memory associated
+  // with it when this class is deleted.
+  talk_base::scoped_refptr<webrtc::LocalVideoTrackInterface> video_track_;
+  // Needed to keep track of number of frames send.
+  FakeVideoCaptureModule* fake_video_capture_module_;
+  // Ensures that fake_video_renderer_ is available as long as this class
+  // exists. It also ensures destruction of the memory associated with it when
+  // this class is deleted.
+  talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+      video_renderer_wrapper_;
+  // Needed to keep track of number of frames received.
+  cricket::FakeVideoRenderer* fake_video_renderer_;
+
+  // For remote peer communication.
+  SignalingMessageReceiver* signaling_message_receiver_;
+};
+
+class P2PTestConductor : public testing::Test {
+ public:
+  virtual void SetUp() {
+    EXPECT_TRUE(Init());
+  }
+  // Return true if session no longer is pending. I.e. if the session is active
+  // or failed.
+  bool ActivationNotPending() {
+    if (!IsInitialized()) {
+      return true;
+    }
+    return SessionActive();
+  }
+  bool SessionActive() {
+    return initiating_client_->SessionActive() &&
+        receiving_client_->SessionActive();
+  }
+  // Return true if the number of frames provided have been received or it is
+  // known that that will never occur (e.g. no frames will be sent or
+  // captured).
+  bool FramesNotPending(int frames_to_receive) {
+    if (!IsInitialized()) {
+      return true;
+    }
+    return FramesReceivedCheck(frames_to_receive);
+  }
+  bool FramesReceivedCheck(int frames_received) {
+    return initiating_client_->FramesReceivedCheck(frames_received) &&
+        receiving_client_->FramesReceivedCheck(frames_received);
+  }
+  ~P2PTestConductor() {
+    if (initiating_client_.get() != NULL) {
+      initiating_client_->set_signaling_message_receiver(NULL);
+    }
+    if (receiving_client_.get() != NULL) {
+      receiving_client_->set_signaling_message_receiver(NULL);
+    }
+  }
+
+  bool StartSession() {
+    if (!IsInitialized()) {
+      return false;
+    }
+    initiating_client_->StartSession();
+    receiving_client_->StartSession();
+    return true;
+  }
+
+  bool StopSession() {
+    if (!IsInitialized()) {
+      return false;
+    }
+    initiating_client_->StopSession();
+    receiving_client_->StopSession();
+    return true;
+  }
+
+ private:
+  bool Init() {
+    initiating_client_.reset(PeerConnectionP2PTestClient::CreateClient(0));
+    receiving_client_.reset(PeerConnectionP2PTestClient::CreateClient(1));
+    if ((initiating_client_.get() == NULL) ||
+        (receiving_client_.get() == NULL)) {
+      return false;
+    }
+    initiating_client_->set_signaling_message_receiver(receiving_client_.get());
+    receiving_client_->set_signaling_message_receiver(initiating_client_.get());
+    return true;
+  }
+  bool IsInitialized() const {
+    return (initiating_client_.get() != NULL) &&
+        (receiving_client_.get() != NULL);
+  }
+
+  talk_base::scoped_ptr<PeerConnectionP2PTestClient> initiating_client_;
+  talk_base::scoped_ptr<PeerConnectionP2PTestClient> receiving_client_;
+};
+
+// This test sets up a call between two parties. Both parties send static frames
+// to each other. Once the test is finished the number of sent frames is
+// compared to the number of received frames.
+TEST_F(P2PTestConductor, LocalP2PTest) {
+  EXPECT_TRUE(StartSession());
+  const int kMaxWaitForActivationMs = 5000;
+  EXPECT_TRUE_WAIT(ActivationNotPending(), kMaxWaitForActivationMs);
+  EXPECT_TRUE(SessionActive());
+
+  // TODO - These are failing on Windows dbg paricular on pulse.
+  // removing check now.
+#if 0
+  const int kEndFrameCount = 10;
+  const int kMaxWaitForFramesMs = 5000;
+  EXPECT_TRUE_WAIT(FramesNotPending(kEndFrameCount), kMaxWaitForFramesMs);
+  EXPECT_TRUE(FramesReceivedCheck(kEndFrameCount));
+#endif
+  EXPECT_TRUE(StopSession());
+}
diff --git a/talk/app/webrtc/peerconnectionfactory_unittest.cc b/talk/app/webrtc/peerconnectionfactory_unittest.cc
new file mode 100644
index 0000000..5056e17
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactory_unittest.cc
@@ -0,0 +1,80 @@
+/*
+ * 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
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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/app/webrtc/mediastreamimpl.h"
+#include "talk/app/webrtc/peerconnectionfactoryimpl.h"
+#include "talk/app/webrtc/fakeportallocatorfactory.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/webrtccommon.h"
+#include "talk/session/phone/webrtcvoe.h"
+
+static const char kStunConfiguration[] = "STUN stun.l.google.com:19302";
+
+namespace webrtc {
+
+class NullPeerConnectionObserver : public PeerConnectionObserver {
+ public:
+  virtual void OnError() {}
+  virtual void OnMessage(const std::string& msg) {}
+  virtual void OnSignalingMessage(const std::string& msg) {}
+  virtual void OnStateChange(StateType state_changed) {}
+  virtual void OnAddStream(MediaStreamInterface* stream) {}
+  virtual void OnRemoveStream(MediaStreamInterface* stream) {}
+};
+
+TEST(PeerConnectionFactory, CreatePCUsingInternalModules) {
+  talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
+      CreatePeerConnectionFactory());
+  ASSERT_TRUE(factory.get() != NULL);
+
+  NullPeerConnectionObserver observer;
+  talk_base::scoped_refptr<PeerConnectionInterface> pc(
+      factory->CreatePeerConnection(kStunConfiguration, &observer));
+
+  EXPECT_TRUE(pc.get() != NULL);
+}
+
+TEST(PeerConnectionFactory, CreatePCUsingExternalModules) {
+  talk_base::scoped_refptr<PortAllocatorFactoryInterface> allocator_factory(
+      FakePortAllocatorFactory::Create());
+
+  talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory =
+      CreatePeerConnectionFactory(talk_base::Thread::Current(),
+                                  talk_base::Thread::Current(),
+                                  allocator_factory.get(),
+                                  NULL);
+  ASSERT_TRUE(factory.get() != NULL);
+
+  NullPeerConnectionObserver observer;
+  talk_base::scoped_refptr<PeerConnectionInterface> pc(
+      factory->CreatePeerConnection(kStunConfiguration, &observer));
+  EXPECT_TRUE(pc.get() != NULL);
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionfactoryimpl.cc b/talk/app/webrtc/peerconnectionfactoryimpl.cc
new file mode 100644
index 0000000..e74e16e
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactoryimpl.cc
@@ -0,0 +1,252 @@
+/*
+ * 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/peerconnectionfactoryimpl.h"
+
+#include "talk/app/webrtc/mediastreamproxy.h"
+#include "talk/app/webrtc/mediastreamtrackproxy.h"
+#include "talk/app/webrtc/peerconnectionimpl.h"
+#include "talk/app/webrtc/portallocatorfactory.h"
+#include "talk/session/phone/dummydevicemanager.h"
+#include "talk/session/phone/webrtcmediaengine.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/audio_device/main/interface/audio_device.h"
+#else
+#include "third_party/webrtc/files/include/audio_device.h"
+#endif
+
+using talk_base::scoped_refptr;
+
+namespace {
+
+typedef talk_base::TypedMessageData<bool> InitMessageData;
+
+struct CreatePeerConnectionParams : public talk_base::MessageData {
+  CreatePeerConnectionParams(const std::string& configuration,
+                             webrtc::PeerConnectionObserver* observer)
+      : configuration(configuration), observer(observer) {
+  }
+  scoped_refptr<webrtc::PeerConnectionInterface> peerconnection;
+  const std::string& configuration;
+  webrtc::PeerConnectionObserver* observer;
+};
+
+enum {
+  MSG_INIT_FACTORY = 1,
+  MSG_TERMINATE_FACTORY = 2,
+  MSG_CREATE_PEERCONNECTION = 3,
+};
+
+}  // namespace
+
+namespace webrtc {
+
+scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory() {
+  talk_base::RefCountedObject<PeerConnectionFactory>* pc_factory =
+      new talk_base::RefCountedObject<PeerConnectionFactory>();
+
+  if (!pc_factory->Initialize()) {
+    delete pc_factory;
+    pc_factory = NULL;
+  }
+  return pc_factory;
+}
+
+scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory(talk_base::Thread* worker_thread,
+                            talk_base::Thread* signaling_thread,
+                            PortAllocatorFactoryInterface* factory,
+                            AudioDeviceModule* default_adm) {
+  talk_base::RefCountedObject<PeerConnectionFactory>* pc_factory =
+      new talk_base::RefCountedObject<PeerConnectionFactory>(
+          worker_thread, signaling_thread, factory, default_adm);
+  if (!pc_factory->Initialize()) {
+    delete pc_factory;
+    pc_factory = NULL;
+  }
+  return pc_factory;
+}
+
+PeerConnectionFactory::PeerConnectionFactory()
+    : owns_ptrs_(true),
+      signaling_thread_(new talk_base::Thread),
+      worker_thread_(new talk_base::Thread) {
+  bool result = signaling_thread_->Start();
+  ASSERT(result);
+  result = worker_thread_->Start();
+  ASSERT(result);
+}
+
+PeerConnectionFactory::PeerConnectionFactory(
+    talk_base::Thread* worker_thread,
+    talk_base::Thread* signaling_thread,
+    PortAllocatorFactoryInterface* port_allocator_factory,
+    AudioDeviceModule* default_adm)
+    : owns_ptrs_(false),
+      signaling_thread_(signaling_thread),
+      worker_thread_(worker_thread),
+      allocator_factory_(port_allocator_factory),
+      default_adm_(default_adm) {
+  ASSERT(worker_thread != NULL);
+  ASSERT(signaling_thread != NULL);
+  ASSERT(allocator_factory_.get() != NULL);
+  // TODO: Currently there is no way creating an external adm in
+  // libjingle source tree. So we can 't currently assert if this is NULL.
+  // ASSERT(default_adm != NULL);
+}
+
+PeerConnectionFactory::~PeerConnectionFactory() {
+  signaling_thread_->Clear(this);
+  signaling_thread_->Send(this, MSG_TERMINATE_FACTORY);
+  if (owns_ptrs_) {
+    delete signaling_thread_;
+    delete worker_thread_;
+  }
+}
+
+bool PeerConnectionFactory::Initialize() {
+  InitMessageData result(false);
+  signaling_thread_->Send(this, MSG_INIT_FACTORY, &result);
+  return result.data();
+}
+
+void PeerConnectionFactory::OnMessage(talk_base::Message* msg) {
+  switch (msg->message_id) {
+    case MSG_INIT_FACTORY: {
+     InitMessageData* pdata = static_cast<InitMessageData*> (msg->pdata);
+     pdata->data() = Initialize_s();
+     break;
+    }
+    case MSG_TERMINATE_FACTORY: {
+      Terminate_s();
+      break;
+    }
+    case MSG_CREATE_PEERCONNECTION: {
+      CreatePeerConnectionParams* pdata =
+          static_cast<CreatePeerConnectionParams*> (msg->pdata);
+      pdata->peerconnection = CreatePeerConnection_s(pdata->configuration,
+                                                     pdata->observer);
+      break;
+    }
+  }
+}
+
+bool PeerConnectionFactory::Initialize_s() {
+  if (owns_ptrs_) {
+    allocator_factory_ = PortAllocatorFactory::Create(worker_thread_);
+    if (allocator_factory_.get() == NULL)
+      return false;
+  }
+
+  cricket::DummyDeviceManager* device_manager(
+      new cricket::DummyDeviceManager());
+  // TODO:  Need to make sure only one VoE is created inside
+  // WebRtcMediaEngine.
+  cricket::WebRtcMediaEngine* webrtc_media_engine(
+      new cricket::WebRtcMediaEngine(default_adm_.get(),
+                                     NULL,   // No secondary adm.
+                                     NULL));  // No vcm available.
+
+  channel_manager_.reset(new cricket::ChannelManager(
+      webrtc_media_engine, device_manager, worker_thread_));
+  if (!channel_manager_->Init()) {
+    return false;
+  }
+  return true;
+}
+
+// Terminate what we created on the signaling thread.
+void PeerConnectionFactory::Terminate_s() {
+  channel_manager_.reset(NULL);
+  if (owns_ptrs_) {
+    allocator_factory_ = NULL;
+  }
+}
+
+scoped_refptr<PeerConnectionInterface>
+PeerConnectionFactory::CreatePeerConnection(
+    const std::string& configuration,
+    PeerConnectionObserver* observer) {
+  CreatePeerConnectionParams params(configuration, observer);
+  signaling_thread_->Send(this, MSG_CREATE_PEERCONNECTION, &params);
+  return params.peerconnection;
+}
+
+scoped_refptr<PeerConnectionInterface>
+PeerConnectionFactory::CreatePeerConnection_s(
+    const std::string& configuration,
+    PeerConnectionObserver* observer) {
+  talk_base::RefCountedObject<PeerConnection>* pc(
+      new talk_base::RefCountedObject<PeerConnection>(this));
+  if (!pc->Initialize(configuration, observer)) {
+    delete pc;
+    pc = NULL;
+  }
+  return pc;
+}
+
+scoped_refptr<LocalMediaStreamInterface>
+PeerConnectionFactory::CreateLocalMediaStream(
+      const std::string& label) {
+  return MediaStreamProxy::Create(label, signaling_thread_);
+}
+
+scoped_refptr<LocalVideoTrackInterface>
+PeerConnectionFactory::CreateLocalVideoTrack(
+    const std::string& label,
+    cricket::VideoCapturer* video_device) {
+  return VideoTrackProxy::CreateLocal(label, video_device,
+                                      signaling_thread_);
+}
+
+scoped_refptr<LocalAudioTrackInterface>
+PeerConnectionFactory::CreateLocalAudioTrack(
+    const std::string& label,
+    AudioDeviceModule* audio_device) {
+  return AudioTrackProxy::CreateLocal(label, audio_device,
+                                      signaling_thread_);
+}
+
+cricket::ChannelManager* PeerConnectionFactory::channel_manager() {
+  return channel_manager_.get();
+}
+
+talk_base::Thread* PeerConnectionFactory::signaling_thread() {
+  return signaling_thread_;
+}
+
+talk_base::Thread* PeerConnectionFactory::worker_thread() {
+  return worker_thread_;
+}
+
+PortAllocatorFactoryInterface* PeerConnectionFactory::port_allocator_factory() {
+  return allocator_factory_.get();
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionfactoryimpl.h b/talk/app/webrtc/peerconnectionfactoryimpl.h
new file mode 100644
index 0000000..ba22d47
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactoryimpl.h
@@ -0,0 +1,94 @@
+/*
+ * 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_APP_WEBRTC_PEERCONNECTIONFACTORYIMPL_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONFACTORYIMPL_H_
+
+#include <string>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/channelmanager.h"
+
+namespace webrtc {
+
+class PeerConnectionFactory : public PeerConnectionFactoryInterface,
+                              public talk_base::MessageHandler {
+ public:
+  talk_base::scoped_refptr<PeerConnectionInterface> CreatePeerConnection(
+      const std::string& config,
+      PeerConnectionObserver* observer);
+  bool Initialize();
+
+  virtual talk_base::scoped_refptr<LocalMediaStreamInterface>
+      CreateLocalMediaStream(const std::string& label);
+
+  virtual talk_base::scoped_refptr<LocalVideoTrackInterface>
+      CreateLocalVideoTrack(const std::string& label,
+                            cricket::VideoCapturer* video_device);
+
+  virtual talk_base::scoped_refptr<LocalAudioTrackInterface>
+      CreateLocalAudioTrack(const std::string& label,
+                            AudioDeviceModule* audio_device);
+
+  virtual cricket::ChannelManager* channel_manager();
+  virtual talk_base::Thread* signaling_thread();
+  virtual talk_base::Thread* worker_thread();
+  virtual PortAllocatorFactoryInterface* port_allocator_factory();
+
+ protected:
+  PeerConnectionFactory();
+  PeerConnectionFactory(
+      talk_base::Thread* worker_thread,
+      talk_base::Thread* signaling_thread,
+      PortAllocatorFactoryInterface* port_allocator_factory,
+      AudioDeviceModule* default_adm);
+  virtual ~PeerConnectionFactory();
+
+
+ private:
+  bool Initialize_s();
+  void Terminate_s();
+  talk_base::scoped_refptr<PeerConnectionInterface> CreatePeerConnection_s(
+      const std::string& configuration,
+      PeerConnectionObserver* observer);
+  // Implements talk_base::MessageHandler.
+  void OnMessage(talk_base::Message* msg);
+
+  bool owns_ptrs_;
+  talk_base::Thread* signaling_thread_;
+  talk_base::Thread* worker_thread_;
+  talk_base::scoped_refptr<PortAllocatorFactoryInterface> allocator_factory_;
+  // External Audio device used for audio playback.
+  talk_base::scoped_refptr<AudioDeviceModule> default_adm_;
+  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTIONFACTORYIMPL_H_
diff --git a/talk/app/webrtc/peerconnectionimpl.cc b/talk/app/webrtc/peerconnectionimpl.cc
index 28e4685..7f990f1 100644
--- a/talk/app/webrtc/peerconnectionimpl.cc
+++ b/talk/app/webrtc/peerconnectionimpl.cc
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * 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:
@@ -27,199 +27,366 @@
 
 #include "talk/app/webrtc/peerconnectionimpl.h"
 
-#include "talk/app/webrtc/webrtcjson.h"
-#include "talk/app/webrtc/webrtcsession.h"
-#include "talk/base/basicpacketsocketfactory.h"
-#include "talk/base/helpers.h"
+#include <vector>
+
+#include "talk/app/webrtc/mediastreamhandler.h"
+#include "talk/app/webrtc/streamcollectionimpl.h"
 #include "talk/base/logging.h"
 #include "talk/base/stringencode.h"
-#include "talk/p2p/base/session.h"
-#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+namespace {
+
+// The number of the tokens in the config string.
+static const size_t kConfigTokens = 2;
+static const size_t kServiceCount = 5;
+// The default stun port.
+static const int kDefaultPort = 3478;
+
+// NOTE: Must be in the same order as the ServiceType enum.
+static const char* kValidServiceTypes[kServiceCount] = {
+    "STUN", "STUNS", "TURN", "TURNS", "INVALID" };
+
+enum ServiceType {
+  STUN,     // Indicates a STUN server.
+  STUNS,    // Indicates a STUN server used with a TLS session.
+  TURN,     // Indicates a TURN server
+  TURNS,    // Indicates a TURN server used with a TLS session.
+  INVALID,  // Unknown.
+};
+
+enum {
+  MSG_COMMITSTREAMCHANGES = 1,
+  MSG_PROCESSSIGNALINGMESSAGE = 2,
+  MSG_RETURNREMOTEMEDIASTREAMS = 3,
+  MSG_CLOSE = 4,
+  MSG_READYSTATE = 5,
+  MSG_SDPSTATE = 6,
+  MSG_TERMINATE = 7
+};
+
+typedef webrtc::PortAllocatorFactoryInterface::StunConfiguration
+    StunConfiguration;
+typedef webrtc::PortAllocatorFactoryInterface::TurnConfiguration
+    TurnConfiguration;
+
+bool static ParseConfigString(const std::string& config,
+                              std::vector<StunConfiguration>* stun_config,
+                              std::vector<TurnConfiguration>* turn_config) {
+  std::vector<std::string> tokens;
+  talk_base::tokenize(config, ' ', &tokens);
+
+  if (tokens.size() != kConfigTokens) {
+    LOG(WARNING) << "Invalid config string";
+    return false;
+  }
+
+  ServiceType service_type = INVALID;
+
+  const std::string& type = tokens[0];
+  for (size_t i = 0; i < kServiceCount; ++i) {
+    if (type.compare(kValidServiceTypes[i]) == 0) {
+      service_type = static_cast<ServiceType>(i);
+      break;
+    }
+  }
+
+  if (service_type == INVALID) {
+    LOG(WARNING) << "Invalid service type: " << type;
+    return false;
+  }
+  std::string service_address = tokens[1];
+
+  int port;
+  tokens.clear();
+  talk_base::tokenize(service_address, ':', &tokens);
+  if (tokens.size() != kConfigTokens) {
+    port = kDefaultPort;
+  } else {
+    port = talk_base::FromString<int>(tokens[1]);
+    if (port <= 0 || port > 0xffff) {
+      LOG(WARNING) << "Invalid port: " << tokens[1];
+      return false;
+    }
+  }
+
+  // TODO: Currently the specification does not tell us how to parse
+  // multiple addresses, username and password from the configuration string.
+  switch (service_type) {
+    case STUN:
+      stun_config->push_back(StunConfiguration(service_address, port));
+      break;
+    case TURN:
+      turn_config->push_back(TurnConfiguration(service_address, port, "", ""));
+      break;
+    case TURNS:
+    case STUNS:
+    case INVALID:
+    default:
+      LOG(WARNING) << "Configuration not supported";
+      return false;
+  }
+  return true;
+}
+
+struct SignalingParams : public talk_base::MessageData {
+  SignalingParams(const std::string& msg,
+                  webrtc::StreamCollectionInterface* local_streams)
+      : msg(msg),
+        local_streams(local_streams) {}
+  const std::string msg;
+  talk_base::scoped_refptr<webrtc::StreamCollectionInterface> local_streams;
+};
+
+struct StreamCollectionParams : public talk_base::MessageData {
+  explicit StreamCollectionParams(webrtc::StreamCollectionInterface* streams)
+      : streams(streams) {}
+  talk_base::scoped_refptr<webrtc::StreamCollectionInterface> streams;
+};
+
+struct ReadyStateMessage : public talk_base::MessageData {
+  ReadyStateMessage() : state(webrtc::PeerConnectionInterface::kNew) {}
+  webrtc::PeerConnectionInterface::ReadyState state;
+};
+
+struct SdpStateMessage : public talk_base::MessageData {
+  SdpStateMessage() : state(webrtc::PeerConnectionInterface::kSdpNew) {}
+  webrtc::PeerConnectionInterface::SdpState state;
+};
+
+}  // namespace
 
 namespace webrtc {
 
-
-PeerConnectionImpl::PeerConnectionImpl(
-    cricket::PortAllocator* port_allocator,
-    cricket::ChannelManager* channel_manager,
-    talk_base::Thread* signaling_thread)
-  : port_allocator_(port_allocator),
-    channel_manager_(channel_manager),
-    signaling_thread_(signaling_thread),
-    event_callback_(NULL),
-    session_(NULL) {
+cricket::VideoCapturer* CreateVideoCapturer(VideoCaptureModule* vcm) {
+  cricket::WebRtcVideoCapturer* video_capturer =
+      new cricket::WebRtcVideoCapturer;
+  if (!video_capturer->Init(vcm)) {
+    delete video_capturer;
+    video_capturer = NULL;
+  }
+  return video_capturer;
 }
 
-PeerConnectionImpl::~PeerConnectionImpl() {
+PeerConnection::PeerConnection(PeerConnectionFactory* factory)
+    : factory_(factory),
+      observer_(NULL),
+      ready_state_(kNew),
+      sdp_state_(kSdpNew),
+      local_media_streams_(StreamCollection::Create()) {
 }
 
-bool PeerConnectionImpl::Init() {
-  std::string sid;
-  talk_base::CreateRandomString(8, &sid);
-  const bool incoming = false;
-  // default outgoing direction
-  session_.reset(CreateMediaSession(sid, incoming));
-  if (session_.get() == NULL) {
-    ASSERT(false && "failed to initialize a session");
+PeerConnection::~PeerConnection() {
+  signaling_thread()->Clear(this);
+  signaling_thread()->Send(this, MSG_TERMINATE);
+}
+
+// Clean up what needs to be cleaned up on the signaling thread.
+void PeerConnection::Terminate_s() {
+  stream_handler_.reset();
+  signaling_.reset();
+  session_.reset();
+  port_allocator_.reset();
+}
+
+bool PeerConnection::Initialize(const std::string& configuration,
+                                PeerConnectionObserver* observer) {
+  ASSERT(observer != NULL);
+  if (!observer)
     return false;
-  }
-  return true;
+  observer_ = observer;
+  std::vector<PortAllocatorFactoryInterface::StunConfiguration> stun_config;
+  std::vector<PortAllocatorFactoryInterface::TurnConfiguration> turn_config;
+
+  ParseConfigString(configuration, &stun_config, &turn_config);
+
+  port_allocator_.reset(factory_->port_allocator_factory()->CreatePortAllocator(
+      stun_config, turn_config));
+
+  session_.reset(new WebRtcSession(factory_->channel_manager(),
+                                   factory_->signaling_thread(),
+                                   factory_->worker_thread(),
+                                   port_allocator_.get()));
+  signaling_.reset(new PeerConnectionSignaling(factory_->signaling_thread(),
+                                               session_.get()));
+  stream_handler_.reset(new MediaStreamHandlers(session_.get()));
+
+  signaling_->SignalNewPeerConnectionMessage.connect(
+      this, &PeerConnection::OnNewPeerConnectionMessage);
+  signaling_->SignalRemoteStreamAdded.connect(
+      this, &PeerConnection::OnRemoteStreamAdded);
+  signaling_->SignalRemoteStreamRemoved.connect(
+      this, &PeerConnection::OnRemoteStreamRemoved);
+  signaling_->SignalStateChange.connect(
+      this, &PeerConnection::OnSignalingStateChange);
+  // Register with WebRtcSession
+  session_->RegisterObserver(signaling_.get());
+
+  // Initialize the WebRtcSession. It creates transport channels etc.
+  const bool result = session_->Initialize();
+  if (result)
+    ChangeReadyState(PeerConnectionInterface::kNegotiating);
+  return result;
 }
 
-void PeerConnectionImpl::RegisterObserver(PeerConnectionObserver* observer) {
-  // This assert is to catch cases where two observer pointers are registered.
-  // We only support one and if another is to be used, the current one must be
-  // cleared first.
-  ASSERT(observer == NULL || event_callback_ == NULL);
-  event_callback_ = observer;
+talk_base::scoped_refptr<StreamCollectionInterface>
+PeerConnection::local_streams() {
+  return local_media_streams_;
 }
 
-bool PeerConnectionImpl::SignalingMessage(
-    const std::string& signaling_message) {
-  // Deserialize signaling message
-  cricket::SessionDescription* incoming_sdp = NULL;
-  std::vector<cricket::Candidate> candidates;
-  if (!ParseJsonSignalingMessage(signaling_message,
-                                 &incoming_sdp, &candidates)) {
-    return false;
-  }
-
-  bool ret = false;
-  if (GetReadyState() == NEW) {
-    // set direction to incoming, as message received first
-    session_->set_incoming(true);
-    ret = session_->OnInitiateMessage(incoming_sdp, candidates);
-  } else {
-    ret = session_->OnRemoteDescription(incoming_sdp, candidates);
-  }
-  return ret;
+talk_base::scoped_refptr<StreamCollectionInterface>
+PeerConnection::remote_streams() {
+  StreamCollectionParams msg(NULL);
+  signaling_thread()->Send(this, MSG_RETURNREMOTEMEDIASTREAMS, &msg);
+  return msg.streams;
 }
 
-WebRtcSession* PeerConnectionImpl::CreateMediaSession(
-    const std::string& id, bool incoming) {
-  ASSERT(port_allocator_ != NULL);
-  WebRtcSession* session = new WebRtcSession(id, incoming,
-      port_allocator_, channel_manager_, signaling_thread_);
-
-  if (session->Initiate()) {
-    session->SignalAddStream.connect(
-        this,
-        &PeerConnectionImpl::OnAddStream);
-    session->SignalRemoveStream.connect(
-        this,
-        &PeerConnectionImpl::OnRemoveStream);
-    session->SignalLocalDescription.connect(
-        this,
-        &PeerConnectionImpl::OnLocalDescription);
-    session->SignalFailedCall.connect(
-        this,
-        &PeerConnectionImpl::OnFailedCall);
-  } else {
-    delete session;
-    session = NULL;
-  }
-  return session;
+void PeerConnection::ProcessSignalingMessage(const std::string& msg) {
+  SignalingParams* parameter(new SignalingParams(
+      msg, StreamCollection::Create(local_media_streams_)));
+  signaling_thread()->Post(this, MSG_PROCESSSIGNALINGMESSAGE, parameter);
 }
 
-bool PeerConnectionImpl::AddStream(const std::string& stream_id, bool video) {
-  bool ret = false;
-  if (session_->HasStream(stream_id)) {
-    ASSERT(false && "A stream with this name already exists");
-  } else {
-    if (!video) {
-      ret = !session_->HasAudioChannel() &&
-            session_->CreateVoiceChannel(stream_id);
-    } else {
-      ret = !session_->HasVideoChannel() &&
-            session_->CreateVideoChannel(stream_id);
+void PeerConnection::AddStream(LocalMediaStreamInterface* local_stream) {
+  local_media_streams_->AddStream(local_stream);
+}
+
+void PeerConnection::RemoveStream(LocalMediaStreamInterface* remove_stream) {
+  local_media_streams_->RemoveStream(remove_stream);
+}
+
+void PeerConnection::CommitStreamChanges() {
+  StreamCollectionParams* msg(new StreamCollectionParams(
+          StreamCollection::Create(local_media_streams_)));
+  signaling_thread()->Post(this, MSG_COMMITSTREAMCHANGES, msg);
+}
+
+void PeerConnection::Close() {
+  signaling_thread()->Send(this, MSG_CLOSE);
+}
+
+PeerConnectionInterface::ReadyState PeerConnection::ready_state() {
+  ReadyStateMessage msg;
+  signaling_thread()->Send(this, MSG_READYSTATE, &msg);
+  return msg.state;
+}
+
+PeerConnectionInterface::SdpState PeerConnection::sdp_state() {
+  SdpStateMessage msg;
+  signaling_thread()->Send(this, MSG_SDPSTATE, &msg);
+  return msg.state;
+}
+
+void PeerConnection::OnMessage(talk_base::Message* msg) {
+  talk_base::MessageData* data = msg->pdata;
+  switch (msg->message_id) {
+    case MSG_COMMITSTREAMCHANGES: {
+      if (ready_state_ != PeerConnectionInterface::kClosed ||
+          ready_state_ != PeerConnectionInterface::kClosing) {
+        StreamCollectionParams* param(
+            static_cast<StreamCollectionParams*> (data));
+        signaling_->CreateOffer(param->streams);
+        stream_handler_->CommitLocalStreams(param->streams);
+      }
+      delete data;  // Because it is Posted.
+      break;
     }
-  }
-  return ret;
-}
-
-bool PeerConnectionImpl::RemoveStream(const std::string& stream_id) {
-  return session_->RemoveStream(stream_id);
-}
-
-void PeerConnectionImpl::OnLocalDescription(
-    const cricket::SessionDescription* desc,
-    const std::vector<cricket::Candidate>& candidates) {
-  if (!desc) {
-    LOG(WARNING) << "no local SDP ";
-    return;
-  }
-
-  std::string message;
-  if (GetJsonSignalingMessage(desc, candidates, &message)) {
-    if (event_callback_) {
-      event_callback_->OnSignalingMessage(message);
+    case MSG_PROCESSSIGNALINGMESSAGE: {
+      if (ready_state_ != PeerConnectionInterface::kClosed) {
+        SignalingParams* params(static_cast<SignalingParams*> (data));
+        signaling_->ProcessSignalingMessage(params->msg, params->local_streams);
+      }
+      delete data;  // Because it is Posted.
+      break;
     }
+    case MSG_RETURNREMOTEMEDIASTREAMS: {
+      StreamCollectionParams* param(
+          static_cast<StreamCollectionParams*> (data));
+      param->streams = StreamCollection::Create(signaling_->remote_streams());
+      break;
+    }
+    case MSG_CLOSE: {
+      if (ready_state_ != PeerConnectionInterface::kClosed) {
+        ChangeReadyState(PeerConnectionInterface::kClosing);
+        signaling_->SendShutDown();
+      }
+      break;
+    }
+    case MSG_READYSTATE: {
+      ReadyStateMessage* msg(static_cast<ReadyStateMessage*> (data));
+      msg->state = ready_state_;
+      break;
+    }
+    case MSG_SDPSTATE: {
+      SdpStateMessage* msg(static_cast<SdpStateMessage*> (data));
+      msg->state = sdp_state_;
+      break;
+    }
+    case MSG_TERMINATE: {
+      Terminate_s();
+      break;
+    }
+    default:
+      ASSERT(!"NOT IMPLEMENTED");
+      break;
   }
 }
 
-void PeerConnectionImpl::OnFailedCall() {
-  // TODO: implement.
+void PeerConnection::OnNewPeerConnectionMessage(const std::string& message) {
+  observer_->OnSignalingMessage(message);
 }
 
-bool PeerConnectionImpl::SetAudioDevice(const std::string& wave_in_device,
-                                        const std::string& wave_out_device,
-                                        int opts) {
-  return channel_manager_->SetAudioOptions(wave_in_device,
-                                           wave_out_device,
-                                           opts);
+void PeerConnection::OnRemoteStreamAdded(MediaStreamInterface* remote_stream) {
+  stream_handler_->AddRemoteStream(remote_stream);
+  observer_->OnAddStream(remote_stream);
 }
 
-bool PeerConnectionImpl::SetLocalVideoRenderer(
-    cricket::VideoRenderer* renderer) {
-  return channel_manager_->SetLocalRenderer(renderer);
+void PeerConnection::OnRemoteStreamRemoved(
+    MediaStreamInterface* remote_stream) {
+  stream_handler_->RemoveRemoteStream(remote_stream);
+  observer_->OnRemoveStream(remote_stream);
 }
 
-bool PeerConnectionImpl::SetVideoRenderer(const std::string& stream_id,
-                                          cricket::VideoRenderer* renderer) {
-  return session_->SetVideoRenderer(stream_id, renderer);
-}
-
-bool PeerConnectionImpl::SetVideoCapture(const std::string& cam_device) {
-  return channel_manager_->SetVideoOptions(cam_device);
-}
-
-bool PeerConnectionImpl::Connect() {
-  return session_->Connect();
-}
-
-// TODO - Close is not used anymore, should be removed.
-bool PeerConnectionImpl::Close() {
-  session_->RemoveAllStreams();
-  return true;
-}
-
-void PeerConnectionImpl::OnAddStream(const std::string& stream_id,
-                                     bool video) {
-  if (event_callback_) {
-    event_callback_->OnAddStream(stream_id, video);
+void PeerConnection::OnSignalingStateChange(
+    PeerConnectionSignaling::State state) {
+  switch (state) {
+    case PeerConnectionSignaling::kInitializing:
+      break;
+    case PeerConnectionSignaling::kIdle:
+      if (ready_state_ == PeerConnectionInterface::kNegotiating)
+        ChangeReadyState(PeerConnectionInterface::kActive);
+      ChangeSdpState(PeerConnectionInterface::kSdpIdle);
+      break;
+    case PeerConnectionSignaling::kWaitingForAnswer:
+      ChangeSdpState(PeerConnectionInterface::kSdpWaiting);
+      break;
+    case PeerConnectionSignaling::kWaitingForOK:
+      ChangeSdpState(PeerConnectionInterface::kSdpWaiting);
+      break;
+    case PeerConnectionSignaling::kShutingDown:
+      ChangeReadyState(PeerConnectionInterface::kClosing);
+      break;
+    case PeerConnectionSignaling::kShutdownComplete:
+      ChangeReadyState(PeerConnectionInterface::kClosed);
+      signaling_thread()->Post(this, MSG_TERMINATE);
+      break;
+    default:
+      ASSERT(!"NOT IMPLEMENTED");
+      break;
   }
 }
 
-void PeerConnectionImpl::OnRemoveStream(const std::string& stream_id,
-                                        bool video) {
-  if (event_callback_) {
-    event_callback_->OnRemoveStream(stream_id, video);
-  }
+void PeerConnection::ChangeReadyState(
+    PeerConnectionInterface::ReadyState ready_state) {
+  ready_state_ = ready_state;
+  observer_->OnStateChange(PeerConnectionObserver::kReadyState);
 }
 
-PeerConnectionImpl::ReadyState PeerConnectionImpl::GetReadyState() {
-  ReadyState ready_state;
-  cricket::BaseSession::State state = session_->state();
-  if (state == cricket::BaseSession::STATE_INIT) {
-    ready_state = NEW;
-  } else if (state == cricket::BaseSession::STATE_INPROGRESS) {
-    ready_state = ACTIVE;
-  } else if (state == cricket::BaseSession::STATE_DEINIT) {
-    ready_state = CLOSED;
-  } else {
-    ready_state = NEGOTIATING;
-  }
-  return ready_state;
+void PeerConnection::ChangeSdpState(
+    PeerConnectionInterface::SdpState sdp_state) {
+  sdp_state_ = sdp_state;
+  observer_->OnStateChange(PeerConnectionObserver::kSdpState);
 }
 
 }  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionimpl.h b/talk/app/webrtc/peerconnectionimpl.h
index 6ff2f25..2686cf2 100644
--- a/talk/app/webrtc/peerconnectionimpl.h
+++ b/talk/app/webrtc/peerconnectionimpl.h
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * 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:
@@ -28,68 +28,83 @@
 #ifndef TALK_APP_WEBRTC_PEERCONNECTIONIMPL_H_
 #define TALK_APP_WEBRTC_PEERCONNECTIONIMPL_H_
 
+#include <map>
 #include <string>
-#include <vector>
 
 #include "talk/app/webrtc/peerconnection.h"
-#include "talk/base/sigslot.h"
+#include "talk/app/webrtc/peerconnectionfactoryimpl.h"
+#include "talk/app/webrtc/peerconnectionsignaling.h"
+#include "talk/app/webrtc/streamcollectionimpl.h"
+#include "talk/app/webrtc/webrtcsession.h"
 #include "talk/base/scoped_ptr.h"
-#include "talk/base/thread.h"
-#include "talk/session/phone/channelmanager.h"
-
-namespace cricket {
-class ChannelManager;
-class PortAllocator;
-class SessionDescription;
-}
+#include "talk/p2p/client/httpportallocator.h"
 
 namespace webrtc {
-class WebRtcSession;
+class MediaStreamHandlers;
 
-class PeerConnectionImpl : public PeerConnection,
-                           public sigslot::has_slots<> {
+// PeerConnectionImpl implements the PeerConnection interface.
+// It uses PeerConnectionSignaling and WebRtcSession to implement
+// the PeerConnection functionality.
+class PeerConnection : public PeerConnectionInterface,
+                       public talk_base::MessageHandler,
+                       public sigslot::has_slots<> {
  public:
-  PeerConnectionImpl(cricket::PortAllocator* port_allocator,
-                     cricket::ChannelManager* channel_manager,
-                     talk_base::Thread* signaling_thread);
-  virtual ~PeerConnectionImpl();
+  explicit PeerConnection(PeerConnectionFactory* factory);
 
-  // PeerConnection interfaces
-  virtual void RegisterObserver(PeerConnectionObserver* observer);
-  virtual bool SignalingMessage(const std::string& msg);
-  virtual bool AddStream(const std::string& stream_id, bool video);
-  virtual bool RemoveStream(const std::string& stream_id);
-  virtual bool Connect();
-  virtual bool Close();
-  virtual bool SetAudioDevice(const std::string& wave_in_device,
-                              const std::string& wave_out_device, int opts);
-  virtual bool SetLocalVideoRenderer(cricket::VideoRenderer* renderer);
-  virtual bool SetVideoRenderer(const std::string& stream_id,
-                                cricket::VideoRenderer* renderer);
-  virtual bool SetVideoCapture(const std::string& cam_device);
-  virtual ReadyState GetReadyState();
+  bool Initialize(const std::string& configuration,
+                  PeerConnectionObserver* observer);
 
-  cricket::ChannelManager* channel_manager() {
-    return channel_manager_;
+  virtual ~PeerConnection();
+
+  virtual void ProcessSignalingMessage(const std::string& msg);
+  virtual bool Send(const std::string& msg) {
+    // TODO: implement
+    ASSERT(false);
+    return false;
   }
-
-  // Callbacks from PeerConnectionImplCallbacks
-  void OnAddStream(const std::string& stream_id, bool video);
-  void OnRemoveStream(const std::string& stream_id, bool video);
-  void OnLocalDescription(
-      const cricket::SessionDescription* desc,
-      const std::vector<cricket::Candidate>& candidates);
-  void OnFailedCall();
-  bool Init();
+  virtual talk_base::scoped_refptr<StreamCollectionInterface> local_streams();
+  virtual talk_base::scoped_refptr<StreamCollectionInterface> remote_streams();
+  virtual void AddStream(LocalMediaStreamInterface* stream);
+  virtual void RemoveStream(LocalMediaStreamInterface* stream);
+  virtual void CommitStreamChanges();
+  virtual void Close();
+  virtual ReadyState ready_state();
+  virtual SdpState sdp_state();
 
  private:
-  WebRtcSession* CreateMediaSession(const std::string& id, bool incoming);
+  // Implement talk_base::MessageHandler.
+  void OnMessage(talk_base::Message* msg);
 
-  cricket::PortAllocator* port_allocator_;
-  cricket::ChannelManager* channel_manager_;
-  talk_base::Thread* signaling_thread_;
-  PeerConnectionObserver* event_callback_;
+  // Signals from PeerConnectionSignaling.
+  void OnNewPeerConnectionMessage(const std::string& message);
+  void OnRemoteStreamAdded(MediaStreamInterface* remote_stream);
+  void OnRemoteStreamRemoved(MediaStreamInterface* remote_stream);
+  void OnSignalingStateChange(PeerConnectionSignaling::State state);
+
+  void ChangeReadyState(PeerConnectionInterface::ReadyState ready_state);
+  void ChangeSdpState(PeerConnectionInterface::SdpState sdp_state);
+  void Terminate_s();
+
+  talk_base::Thread* signaling_thread() {
+    return factory_->signaling_thread();
+  }
+
+  // Storing the factory as a scoped reference pointer ensures that the memory
+  // in the PeerConnectionFactoryImpl remains available as long as the
+  // PeerConnection is running. It is passed to PeerConnection as a raw pointer.
+  // However, since the reference counting is done in the
+  // PeerConnectionFactoryInteface all instances created using the raw pointer
+  // will refer to the same reference count.
+  talk_base::scoped_refptr<PeerConnectionFactory> factory_;
+  PeerConnectionObserver* observer_;
+  ReadyState ready_state_;
+  SdpState sdp_state_;
+  talk_base::scoped_refptr<StreamCollection> local_media_streams_;
+
+  talk_base::scoped_ptr<cricket::PortAllocator> port_allocator_;
   talk_base::scoped_ptr<WebRtcSession> session_;
+  talk_base::scoped_ptr<PeerConnectionSignaling> signaling_;
+  talk_base::scoped_ptr<MediaStreamHandlers> stream_handler_;
 };
 
 }  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionimpl_unittest.cc b/talk/app/webrtc/peerconnectionimpl_unittest.cc
new file mode 100644
index 0000000..5363af6
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionimpl_unittest.cc
@@ -0,0 +1,287 @@
+/*
+ * 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 <string>
+
+#include "talk/app/webrtc/fakeportallocatorfactory.h"
+#include "talk/app/webrtc/mediastreamimpl.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtc/peerconnectionimpl.h"
+#include "talk/app/webrtc/roapmessages.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/base/gunit.h"
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStreamLabel2[] = "local_stream_2";
+static const char kStunConfiguration[] = "STUN stun.l.google.com:19302";
+static const char kInvalidConfiguration[] = "a13151913541234:19302";
+static const uint32 kTimeout = 5000U;
+
+using talk_base::scoped_ptr;
+using talk_base::scoped_refptr;
+using webrtc::FakePortAllocatorFactory;
+using webrtc::LocalMediaStreamInterface;
+using webrtc::LocalVideoTrackInterface;
+using webrtc::MediaStreamInterface;
+using webrtc::PeerConnectionInterface;
+using webrtc::PeerConnectionObserver;
+using webrtc::PortAllocatorFactoryInterface;
+using webrtc::RoapMessageBase;
+using webrtc::RoapOffer;
+
+
+// Create ROAP message for shutdown.
+static std::string CreateShutdownMessage() {
+  webrtc::RoapShutdown shutdown("dummy_session", "", "", 1);
+  return shutdown.Serialize();
+}
+
+// Create a ROAP answer message.
+// The session description in the answer is set to the same as in the offer.
+static std::string CreateAnswerMessage(const RoapMessageBase& msg) {
+  webrtc::RoapOffer offer(msg);
+  EXPECT_TRUE(offer.Parse());
+  cricket::SessionDescription* sdp_offer =
+      offer.ReleaseSessionDescription();
+  const cricket::ContentInfo* audio_content = GetFirstAudioContent(sdp_offer);
+  if (audio_content) {
+    const cricket::AudioContentDescription* desc =
+        static_cast<const cricket::AudioContentDescription*>(
+            audio_content->description);
+    cricket::CryptoParamsVec& cryptos =
+        const_cast<cricket::CryptoParamsVec&>(desc->cryptos());
+    cryptos.erase(cryptos.begin()++);
+  }
+
+  webrtc::RoapAnswer answer(offer.offer_session_id(), "dummy_session",
+                            offer.session_token(), offer.response_token(),
+                            offer.seq(), sdp_offer, offer.candidates());
+  return answer.Serialize();
+}
+
+// Create ROAP message to answer ok to a ROAP shutdown or ROAP answer message.
+static std::string CreateOkMessage(const RoapMessageBase& msg) {
+  webrtc::RoapOk ok(msg.offer_session_id(), "dummy_session",
+                    msg.session_token(), msg.response_token(), msg.seq());
+  return ok.Serialize();
+}
+
+class MockPeerConnectionObserver : public PeerConnectionObserver {
+ public:
+  void SetPeerConnectionInterface(PeerConnectionInterface* pc) {
+    pc_ = pc;
+    state_ = pc_->ready_state();
+    sdp_state_ = pc_->sdp_state();
+  }
+  virtual void OnError() {}
+  virtual void OnMessage(const std::string& msg) {}
+  virtual void OnSignalingMessage(const std::string& msg) {
+    EXPECT_TRUE(last_message_.Parse(msg));
+  }
+  virtual void OnStateChange(StateType state_changed) {
+    if (pc_.get() == NULL)
+      return;
+    switch (state_changed) {
+      case kReadyState:
+        state_ = pc_->ready_state();
+        break;
+      case kSdpState:
+        sdp_state_ = pc_->sdp_state();
+        break;
+      case kIceState:
+        ADD_FAILURE();
+        break;
+      default:
+        ADD_FAILURE();
+        break;
+    }
+  }
+  virtual void OnAddStream(MediaStreamInterface* stream) {
+    last_added_stream_ = stream;
+  }
+  virtual void OnRemoveStream(MediaStreamInterface* stream) {
+    last_removed_stream_ = stream;
+  }
+
+  // Returns the label of the last added stream.
+  // Empty string if no stream have been added.
+  std::string GetLastAddedStreamLabel() {
+    if (last_added_stream_.get())
+      return last_added_stream_->label();
+    return "";
+  }
+  std::string GetLastRemovedStreamLabel() {
+    if (last_removed_stream_.get())
+      return last_removed_stream_->label();
+    return "";
+  }
+
+  talk_base::scoped_refptr<PeerConnectionInterface> pc_;
+  RoapMessageBase last_message_;
+  PeerConnectionInterface::ReadyState state_;
+  PeerConnectionInterface::SdpState sdp_state_;
+
+ private:
+  scoped_refptr<MediaStreamInterface> last_added_stream_;
+  scoped_refptr<MediaStreamInterface> last_removed_stream_;
+};
+
+class PeerConnectionImplTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    port_allocator_factory_ = FakePortAllocatorFactory::Create();
+
+    pc_factory_ = webrtc::CreatePeerConnectionFactory(
+        talk_base::Thread::Current(), talk_base::Thread::Current(),
+        port_allocator_factory_.get(), NULL);
+    ASSERT_TRUE(pc_factory_.get() != NULL);
+  }
+
+  void CreatePeerConnection() {
+    pc_ = pc_factory_->CreatePeerConnection(kStunConfiguration, &observer_);
+    ASSERT_TRUE(pc_.get() != NULL);
+    observer_.SetPeerConnectionInterface(pc_.get());
+    EXPECT_EQ(PeerConnectionInterface::kNegotiating, observer_.state_);
+  }
+
+  void CreatePeerConnectionWithInvalidConfiguration() {
+    pc_ = pc_factory_->CreatePeerConnection(kInvalidConfiguration, &observer_);
+    ASSERT_TRUE(pc_.get() != NULL);
+    observer_.SetPeerConnectionInterface(pc_.get());
+    EXPECT_EQ(PeerConnectionInterface::kNegotiating, observer_.state_);
+  }
+
+  void AddStream(const std::string& label) {
+    // Create a local stream.
+    scoped_refptr<LocalMediaStreamInterface> stream(
+        pc_factory_->CreateLocalMediaStream(label));
+    scoped_refptr<LocalVideoTrackInterface> video_track(
+        pc_factory_->CreateLocalVideoTrack(label, NULL));
+    stream->AddTrack(video_track.get());
+    pc_->AddStream(stream);
+    pc_->CommitStreamChanges();
+
+    EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpWaiting, observer_.sdp_state_,
+                   kTimeout);
+    // Wait for the ICE agent to find the candidates and send an offer.
+    EXPECT_EQ_WAIT(RoapMessageBase::kOffer, observer_.last_message_.type(),
+                   kTimeout);
+  }
+
+  scoped_refptr<PortAllocatorFactoryInterface> port_allocator_factory_;
+  scoped_refptr<webrtc::PeerConnectionFactoryInterface> pc_factory_;
+  scoped_refptr<PeerConnectionInterface> pc_;
+  MockPeerConnectionObserver observer_;
+};
+
+TEST_F(PeerConnectionImplTest, CreatePeerConnectionWithInvalidConfiguration) {
+  CreatePeerConnectionWithInvalidConfiguration();
+  AddStream(kStreamLabel1);
+}
+
+TEST_F(PeerConnectionImplTest, AddStream) {
+  CreatePeerConnection();
+  AddStream(kStreamLabel1);
+  ASSERT_EQ(1u, pc_->local_streams()->count());
+  EXPECT_EQ(kStreamLabel1, pc_->local_streams()->at(0)->label());
+
+  EXPECT_EQ_WAIT(PeerConnectionInterface::kNegotiating, observer_.state_,
+                 kTimeout);
+  pc_->ProcessSignalingMessage(CreateAnswerMessage(observer_.last_message_));
+  EXPECT_EQ_WAIT(PeerConnectionInterface::kActive, observer_.state_, kTimeout);
+  EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpIdle, observer_.sdp_state_,
+                 kTimeout);
+  // Since we answer with the same session description as we offer we can
+  // check if OnAddStream have been called.
+  EXPECT_EQ(kStreamLabel1, observer_.GetLastAddedStreamLabel());
+  ASSERT_EQ(1u, pc_->remote_streams()->count());
+  EXPECT_EQ(kStreamLabel1, pc_->remote_streams()->at(0)->label());
+}
+
+TEST_F(PeerConnectionImplTest, DISABLED_UpdateStream) {
+  CreatePeerConnection();
+  AddStream(kStreamLabel1);
+  WAIT(PeerConnectionInterface::kNegotiating == observer_.state_, kTimeout);
+  pc_->ProcessSignalingMessage(CreateAnswerMessage(observer_.last_message_));
+  WAIT(PeerConnectionInterface::kActive ==  observer_.state_, kTimeout);
+  WAIT(PeerConnectionInterface::kSdpIdle == observer_.sdp_state_, kTimeout);
+
+  AddStream(kStreamLabel2);
+  ASSERT_EQ(2u, pc_->local_streams()->count());
+  EXPECT_EQ(kStreamLabel2, pc_->local_streams()->at(1)->label());
+  EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpWaiting, observer_.sdp_state_,
+                 kTimeout);
+  EXPECT_EQ(PeerConnectionInterface::kActive, observer_.state_);
+  pc_->ProcessSignalingMessage(CreateAnswerMessage(observer_.last_message_));
+  EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpIdle, observer_.sdp_state_,
+                 kTimeout);
+  // Since we answer with the same session description as we offer we can
+  // check if OnAddStream have been called.
+  EXPECT_EQ(kStreamLabel2, observer_.GetLastAddedStreamLabel());
+  ASSERT_EQ(2u, pc_->remote_streams()->count());
+  EXPECT_EQ(kStreamLabel2, pc_->remote_streams()->at(1)->label());
+
+  pc_->RemoveStream(static_cast<LocalMediaStreamInterface*>(
+      pc_->local_streams()->at(1)));
+  pc_->CommitStreamChanges();
+  EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpWaiting, observer_.sdp_state_,
+                 kTimeout);
+  pc_->ProcessSignalingMessage(CreateAnswerMessage(observer_.last_message_));
+  EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpIdle, observer_.sdp_state_,
+                 kTimeout);
+  EXPECT_EQ(kStreamLabel2, observer_.GetLastRemovedStreamLabel());
+  EXPECT_EQ(1u, pc_->local_streams()->count());
+}
+
+TEST_F(PeerConnectionImplTest, SendClose) {
+  CreatePeerConnection();
+  pc_->Close();
+  EXPECT_EQ(RoapMessageBase::kShutdown, observer_.last_message_.type());
+  EXPECT_EQ(PeerConnectionInterface::kClosing, observer_.state_);
+  pc_->ProcessSignalingMessage(CreateOkMessage(observer_.last_message_));
+  EXPECT_EQ_WAIT(PeerConnectionInterface::kClosed, observer_.state_, kTimeout);
+}
+
+TEST_F(PeerConnectionImplTest, ReceiveClose) {
+  CreatePeerConnection();
+  pc_->ProcessSignalingMessage(CreateShutdownMessage());
+  EXPECT_EQ_WAIT(RoapMessageBase::kOk, observer_.last_message_.type(),
+                 kTimeout);
+  EXPECT_EQ(PeerConnectionInterface::kClosed, observer_.state_);
+}
+
+TEST_F(PeerConnectionImplTest, ReceiveCloseWhileExpectingAnswer) {
+  CreatePeerConnection();
+  AddStream(kStreamLabel1);
+
+  // Receive the shutdown message.
+  pc_->ProcessSignalingMessage(CreateShutdownMessage());
+  EXPECT_EQ_WAIT(RoapMessageBase::kOk, observer_.last_message_.type(),
+                 kTimeout);
+  EXPECT_EQ(PeerConnectionInterface::kClosed, observer_.state_);
+}
diff --git a/talk/app/webrtc/peerconnectionsignaling.cc b/talk/app/webrtc/peerconnectionsignaling.cc
new file mode 100644
index 0000000..a3bfb63
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionsignaling.cc
@@ -0,0 +1,596 @@
+/*
+ * 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/app/webrtc/peerconnectionsignaling.h"
+
+#include <utility>
+
+#include "talk/app/webrtc/mediastreamproxy.h"
+#include "talk/app/webrtc/mediastreamtrackproxy.h"
+#include "talk/app/webrtc/sessiondescriptionprovider.h"
+#include "talk/app/webrtc/streamcollectionimpl.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagequeue.h"
+#include "talk/session/phone/channelmanager.h"
+
+using talk_base::scoped_refptr;
+
+namespace webrtc {
+
+enum {
+  MSG_SEND_QUEUED_OFFER = 1,
+  MSG_GENERATE_ANSWER = 2,
+};
+
+// Verifies that a SessionDescription contains as least one valid media content
+// and a valid codec.
+static bool VerifyAnswer(const cricket::SessionDescription* answer_desc) {
+  // We need to verify that at least one media content with
+  // a codec is available.
+  const cricket::ContentInfo* audio_content =
+      GetFirstAudioContent(answer_desc);
+  if (audio_content) {
+    const cricket::AudioContentDescription* audio_desc =
+        static_cast<const cricket::AudioContentDescription*>(
+            audio_content->description);
+    if (audio_desc->codecs().size() > 0) {
+      return true;
+    }
+  }
+  const cricket::ContentInfo* video_content =
+      GetFirstVideoContent(answer_desc);
+  if (video_content) {
+    const cricket::VideoContentDescription* video_desc =
+        static_cast<const cricket::VideoContentDescription*>(
+            video_content->description);
+    if (video_desc->codecs().size() > 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+// Fills a MediaSessionOptions struct with the MediaTracks we want to sent given
+// the local MediaStreams. |local_streams| can be null if there are no tracks to
+// send.
+static void InitMediaSessionOptions(
+    cricket::MediaSessionOptions* options,
+    StreamCollectionInterface* local_streams) {
+  if (!VERIFY(options != NULL))
+      return;
+  // In order to be able to receive video,
+  // the has_video should always be true even if there are no video tracks.
+  options->has_video = true;
+  if (local_streams == NULL)
+    return;
+
+  for (size_t i = 0; i < local_streams->count(); ++i) {
+    MediaStreamInterface* stream = local_streams->at(i);
+
+    scoped_refptr<AudioTracks> audio_tracks(stream->audio_tracks());
+    // For each audio track in the stream, add it to the MediaSessionOptions.
+    for (size_t j = 0; j < audio_tracks->count(); ++j) {
+      scoped_refptr<MediaStreamTrackInterface> track(audio_tracks->at(j));
+      options->AddStream(cricket::MEDIA_TYPE_AUDIO, track->label(),
+                         stream->label());
+    }
+
+    scoped_refptr<VideoTracks> video_tracks(stream->video_tracks());
+    // For each video track in the stream, add it to the MediaSessionOptions.
+    for (size_t j = 0; j <  video_tracks->count(); ++j) {
+      scoped_refptr<MediaStreamTrackInterface> track(video_tracks->at(j));
+      options->AddStream(cricket::MEDIA_TYPE_VIDEO, track->label(),
+                         stream->label());
+    }
+  }
+}
+
+PeerConnectionSignaling::PeerConnectionSignaling(
+    talk_base::Thread* signaling_thread,
+    SessionDescriptionProvider* provider)
+    : signaling_thread_(signaling_thread),
+      provider_(provider),
+      state_(kInitializing),
+      received_pre_offer_(false),
+      remote_streams_(StreamCollection::Create()),
+      local_streams_(StreamCollection::Create()) {
+}
+
+PeerConnectionSignaling::~PeerConnectionSignaling() {}
+
+void PeerConnectionSignaling::OnCandidatesReady(
+    const cricket::Candidates& candidates) {
+  if (!VERIFY(state_ == kInitializing))
+    return;
+  // Store the candidates.
+  candidates_ = candidates;
+  // If we have a queued remote offer we need to handle this first.
+  if (received_pre_offer_) {
+    received_pre_offer_ = false;
+    ChangeState(kWaitingForOK);
+    signaling_thread_->Post(this, MSG_GENERATE_ANSWER);
+  } else if (!queued_local_streams_.empty()) {
+    // Else if we have local queued offers.
+    ChangeState(kWaitingForAnswer);
+    signaling_thread_->Post(this, MSG_SEND_QUEUED_OFFER);
+  } else {
+    ChangeState(kIdle);
+  }
+}
+
+void PeerConnectionSignaling::ChangeState(State new_state) {
+  state_ = new_state;
+  SignalStateChange(state_);
+}
+
+void PeerConnectionSignaling::ProcessSignalingMessage(
+    const std::string& message,
+    StreamCollectionInterface* local_streams) {
+  ASSERT(talk_base::Thread::Current() == signaling_thread_);
+
+  RoapSession::ParseResult result = roap_session_.Parse(message);
+
+  // Signal an error message and return if a message is received after shutdown
+  // or it is not an ok message that is received during shutdown.
+  // No other messages from the remote peer can be processed in these states.
+  if (state_ == kShutdownComplete ||
+      (state_ == kShutingDown && result != RoapSession::kOk)) {
+    SignalNewPeerConnectionMessage(roap_session_.CreateErrorMessage(kNoMatch));
+    return;
+  }
+
+  switch (result) {
+    case RoapSession::kOffer: {
+      queued_local_streams_.clear();
+      queued_local_streams_.push_back(local_streams);
+
+      if (state_ == kWaitingForAnswer) {
+        // Message received out of order or Glare occurred and the decision was
+        // to use the incoming offer.
+        LOG(LS_INFO) << "Received offer while waiting for answer.";
+        // Be nice and handle this offer instead of the pending offer.
+        signaling_thread_->Clear(this, MSG_SEND_QUEUED_OFFER);
+      }
+
+      // Provide the remote session description and the remote candidates from
+      // the parsed ROAP message to the |provider_|.
+      // The session description ownership is transferred from |roap_session_|
+      // to |provider_|.
+      provider_->SetRemoteDescription(roap_session_.ReleaseRemoteDescription(),
+                                      cricket::CA_OFFER);
+      provider_->SetRemoteCandidates(roap_session_.RemoteCandidates());
+      // Update the known remote MediaStreams.
+      UpdateRemoteStreams(provider_->remote_description());
+
+      // If we are still Initializing we need to wait until we have our local
+      // candidates before we can handle the offer. Queue it and handle it when
+      // the state changes.
+      if (state_ == kInitializing) {
+        received_pre_offer_ = true;
+        break;
+      }
+
+      // Post a task to generate the answer.
+      signaling_thread_->Post(this, MSG_GENERATE_ANSWER);
+      ChangeState(kWaitingForOK);
+      break;
+    }
+    case RoapSession::kAnswerMoreComing: {
+      // We ignore this message for now and wait for the complete result.
+      LOG(LS_INFO) << "Received answer more coming.";
+      break;
+    }
+    case RoapSession::kAnswer: {
+      if (state_ != kWaitingForAnswer) {
+        LOG(LS_WARNING) << "Received an unexpected answer.";
+        return;
+      }
+
+      talk_base::scoped_ptr<cricket::SessionDescription> remote_desc(
+          roap_session_.ReleaseRemoteDescription());
+
+      // Pop the first item of queued StreamCollections containing local
+      // MediaStreams that just have been negotiated.
+      scoped_refptr<StreamCollectionInterface> streams(
+          queued_local_streams_.front());
+      queued_local_streams_.pop_front();
+      // Update the state of the local MediaStreams.
+      UpdateSendingLocalStreams(remote_desc.get(), streams);
+
+      // Hand the ownership of the local session description to |provider_|.
+      provider_->SetLocalDescription(local_desc_.release(), cricket::CA_OFFER);
+
+      // Provide the remote session description and the remote candidates from
+      // the parsed ROAP message to the |provider_|.
+      // The session description ownership is transferred from |roap_session_|
+      // to |provider_|.
+      provider_->SetRemoteDescription(remote_desc.release(),
+                                      cricket::CA_ANSWER);
+      provider_->SetRemoteCandidates(roap_session_.RemoteCandidates());
+      // Update the list of known remote MediaStreams.
+      UpdateRemoteStreams(provider_->remote_description());
+
+      // Let the remote peer know we have received the answer.
+      SignalNewPeerConnectionMessage(roap_session_.CreateOk());
+      // Check if we have more offers waiting in the queue.
+      if (!queued_local_streams_.empty()) {
+        // Send the next offer.
+        signaling_thread_->Post(this, MSG_SEND_QUEUED_OFFER);
+      } else {
+        ChangeState(kIdle);
+      }
+      break;
+    }
+    case RoapSession::kOk: {
+      if (state_ == kWaitingForOK) {
+        scoped_refptr<StreamCollectionInterface> streams(
+            queued_local_streams_.front());
+        queued_local_streams_.pop_front();
+        // Update the state of the local streams.
+        UpdateSendingLocalStreams(local_desc_.get(), streams);
+
+        // Hand over the ownership of the local description to the provider.
+        provider_->SetLocalDescription(local_desc_.release(),
+                                       cricket::CA_ANSWER);
+        ChangeState(kIdle);
+        // Check if we have an updated offer waiting in the queue.
+        if (!queued_local_streams_.empty())
+          signaling_thread_->Post(this, MSG_SEND_QUEUED_OFFER);
+      } else if (state_ == kShutingDown) {
+        ChangeState(kShutdownComplete);
+      }
+      break;
+    }
+    case RoapSession::kConflict: {
+      SignalNewPeerConnectionMessage(roap_session_.CreateErrorMessage(
+          kConflict));
+      break;
+    }
+    case RoapSession::kDoubleConflict: {
+      SignalNewPeerConnectionMessage(roap_session_.CreateErrorMessage(
+          kDoubleConflict));
+
+      // Recreate the offer with new sequence values etc.
+      ChangeState(kWaitingForAnswer);
+      signaling_thread_->Post(this, MSG_SEND_QUEUED_OFFER);
+      break;
+    }
+    case RoapSession::kError: {
+      if (roap_session_.RemoteError() != kConflict &&
+          roap_session_.RemoteError() != kDoubleConflict) {
+        SignalErrorMessageReceived(roap_session_.RemoteError());
+        // An error have occurred that we can't do anything about.
+        // Reset the state and wait for user action.
+        signaling_thread_->Clear(this);
+        queued_local_streams_.clear();
+        ChangeState(kIdle);
+      }
+      break;
+    }
+    case RoapSession::kShutDown: {
+      DoShutDown();
+      SignalNewPeerConnectionMessage(roap_session_.CreateOk());
+      ChangeState(kShutdownComplete);
+      break;
+    }
+    case RoapSession::kInvalidMessage: {
+      SignalNewPeerConnectionMessage(roap_session_.CreateErrorMessage(
+          kNoMatch));
+      return;
+    }
+  }
+}
+
+void PeerConnectionSignaling::CreateOffer(
+    StreamCollectionInterface* local_streams) {
+  if (!VERIFY(talk_base::Thread::Current() == signaling_thread_ &&
+              state_ != kShutingDown && state_ != kShutdownComplete)) {
+    return;
+  }
+
+  queued_local_streams_.push_back(local_streams);
+  if (state_ == kIdle) {
+    // Check if we can send a new offer.
+    // Only one offer is allowed at the time.
+    ChangeState(kWaitingForAnswer);
+    signaling_thread_->Post(this, MSG_SEND_QUEUED_OFFER);
+  }
+}
+
+void PeerConnectionSignaling::SendShutDown() {
+  DoShutDown();
+  SignalNewPeerConnectionMessage(roap_session_.CreateShutDown());
+}
+
+// Implement talk_base::MessageHandler.
+void PeerConnectionSignaling::OnMessage(talk_base::Message* msg) {
+  switch (msg->message_id) {
+    case MSG_SEND_QUEUED_OFFER:
+      CreateOffer_s();
+      break;
+    case MSG_GENERATE_ANSWER:
+      CreateAnswer_s();
+      break;
+    default:
+      ASSERT(!"Invalid value in switch statement.");
+      break;
+  }
+}
+
+void PeerConnectionSignaling::CreateOffer_s() {
+  ASSERT(!queued_local_streams_.empty());
+  scoped_refptr<StreamCollectionInterface> local_streams(
+      queued_local_streams_.front());
+  cricket::MediaSessionOptions options;
+  InitMediaSessionOptions(&options, local_streams);
+
+  local_desc_.reset(provider_->CreateOffer(options));
+  SignalNewPeerConnectionMessage(roap_session_.CreateOffer(local_desc_.get(),
+                                                           candidates_));
+}
+
+void PeerConnectionSignaling::DoShutDown() {
+  ChangeState(kShutingDown);
+  signaling_thread_->Clear(this);  // Don't send queued offers or answers.
+  queued_local_streams_.clear();
+  cricket::MediaSessionOptions options;
+  InitMediaSessionOptions(&options, NULL);
+
+  // Create new empty session descriptions without StreamParams.
+  // By applying these descriptions we don't send or receive any streams.
+  const SessionDescription* local_desc = provider_->CreateOffer(options);
+  SessionDescription* remote_desc = provider_->CreateAnswer(local_desc,
+                                                            options);
+
+  provider_->SetRemoteDescription(remote_desc, cricket::CA_OFFER);
+  provider_->SetLocalDescription(local_desc, cricket::CA_ANSWER);
+
+  UpdateRemoteStreams(NULL);
+}
+
+void PeerConnectionSignaling::CreateAnswer_s() {
+  scoped_refptr<StreamCollectionInterface> streams(
+      queued_local_streams_.back());
+  // Clean up all queued collections of local streams except the last one.
+  // The last one is kept until the ok message is received for this answer and
+  // is needed for updating the state of the local streams.
+  queued_local_streams_.erase(queued_local_streams_.begin(),
+                              --queued_local_streams_.end());
+
+  // Create a MediaSessionOptions object with the sources we want to send.
+  cricket::MediaSessionOptions options;
+  InitMediaSessionOptions(&options, streams);
+  // Create an local session description based on this.
+  local_desc_.reset(provider_->CreateAnswer(provider_->remote_description(),
+                                            options));
+  if (!VerifyAnswer(local_desc_.get())) {
+    SignalNewPeerConnectionMessage(roap_session_.CreateErrorMessage(kRefused));
+    return;
+  }
+
+  SignalNewPeerConnectionMessage(roap_session_.CreateAnswer(local_desc_.get(),
+                                                            candidates_));
+}
+
+// Updates or Creates remote MediaStream objects given a
+// remote SessionDesription.
+// If the remote SessionDesription contain new remote MediaStreams
+// SignalRemoteStreamAdded is triggered. If a remote MediaStream is missing from
+// the remote SessionDescription SignalRemoteStreamRemoved is triggered.
+void PeerConnectionSignaling::UpdateRemoteStreams(
+    const cricket::SessionDescription* remote_desc) {
+  talk_base::scoped_refptr<StreamCollection> current_streams(
+      StreamCollection::Create());
+
+  const cricket::ContentInfo* audio_content = GetFirstAudioContent(remote_desc);
+  if (audio_content) {
+    const cricket::AudioContentDescription* audio_desc =
+        static_cast<const cricket::AudioContentDescription*>(
+            audio_content->description);
+
+    for (cricket::StreamParamsVec::const_iterator it =
+             audio_desc->streams().begin();
+         it != audio_desc->streams().end(); ++it) {
+      MediaStreamInterface* old_stream = remote_streams_->find(it->sync_label);
+      scoped_refptr<MediaStreamProxy> new_stream(static_cast<MediaStreamProxy*>(
+          current_streams->find(it->sync_label)));
+
+      if (old_stream == NULL) {
+        if (new_stream == NULL) {
+          // New stream
+          new_stream = MediaStreamProxy::Create(it->sync_label,
+                                                signaling_thread_);
+          current_streams->AddStream(new_stream);
+        }
+        scoped_refptr<AudioTrackInterface> track(
+            AudioTrackProxy::CreateRemote(it->name, signaling_thread_));
+        track->set_state(MediaStreamTrackInterface::kLive);
+        new_stream->AddTrack(track);
+      } else {
+        current_streams->AddStream(old_stream);
+      }
+    }
+  }
+
+  const cricket::ContentInfo* video_content = GetFirstVideoContent(remote_desc);
+  if (video_content) {
+    const cricket::VideoContentDescription* video_desc =
+        static_cast<const cricket::VideoContentDescription*>(
+            video_content->description);
+
+    for (cricket::StreamParamsVec::const_iterator it =
+             video_desc->streams().begin();
+         it != video_desc->streams().end(); ++it) {
+      MediaStreamInterface* old_stream = remote_streams_->find(it->sync_label);
+      scoped_refptr<MediaStreamProxy> new_stream(static_cast<MediaStreamProxy*>(
+          current_streams->find(it->sync_label)));
+      if (old_stream == NULL) {
+        if (new_stream == NULL) {
+          // New stream
+          new_stream = MediaStreamProxy::Create(it->sync_label,
+                                                signaling_thread_);
+          current_streams->AddStream(new_stream);
+        }
+        scoped_refptr<VideoTrackInterface> track(
+            VideoTrackProxy::CreateRemote(it->name, signaling_thread_));
+        new_stream->AddTrack(track);
+        track->set_state(MediaStreamTrackInterface::kLive);
+      } else {
+        current_streams->AddStream(old_stream);
+      }
+    }
+  }
+
+  // Iterate current_streams to find all new streams.
+  // Change the state of the new stream and SignalRemoteStreamAdded.
+  for (size_t i = 0; i < current_streams->count(); ++i) {
+    MediaStreamInterface* new_stream = current_streams->at(i);
+    MediaStreamInterface* old_stream = remote_streams_->find(
+        new_stream->label());
+    if (old_stream != NULL) continue;
+
+    new_stream->set_ready_state(MediaStreamInterface::kLive);
+    SignalRemoteStreamAdded(new_stream);
+  }
+
+  // Iterate the old list of remote streams.
+  // If a stream is not found in the new list it have been removed.
+  // Change the state of the removed stream and SignalRemoteStreamRemoved.
+  for (size_t i = 0; i < remote_streams_->count(); ++i) {
+    MediaStreamInterface* old_stream = remote_streams_->at(i);
+    MediaStreamInterface* new_stream = current_streams->find(
+        old_stream->label());
+    if (new_stream != NULL) continue;
+
+    old_stream->set_ready_state(MediaStreamInterface::kEnded);
+    scoped_refptr<AudioTracks> audio_tracklist(old_stream->audio_tracks());
+    for (size_t j = 0; j < audio_tracklist->count(); ++j) {
+      audio_tracklist->at(j)->set_state(MediaStreamTrackInterface::kEnded);
+    }
+    scoped_refptr<VideoTracks> video_tracklist(old_stream->video_tracks());
+    for (size_t j = 0; j < video_tracklist->count(); ++j) {
+      video_tracklist->at(j)->set_state(MediaStreamTrackInterface::kEnded);
+    }
+    SignalRemoteStreamRemoved(old_stream);
+  }
+  // Prepare for next offer.
+  remote_streams_ = current_streams;
+}
+
+// Update the state of all local streams we have just negotiated. If the
+// negotiation succeeded the state is changed to kLive, if the negotiation
+// failed the state is changed to kEnded.
+void PeerConnectionSignaling::UpdateSendingLocalStreams(
+    const cricket::SessionDescription* answer_desc,
+    StreamCollectionInterface* negotiated_streams) {
+  talk_base::scoped_refptr<StreamCollection> current_local_streams(
+      StreamCollection::Create());
+
+  for (size_t i = 0; i < negotiated_streams->count(); ++i) {
+    scoped_refptr<MediaStreamInterface> stream(negotiated_streams->at(i));
+    scoped_refptr<AudioTracks> audiotracklist(stream->audio_tracks());
+    scoped_refptr<VideoTracks> videotracklist(stream->video_tracks());
+
+    bool stream_ok = false;  // A stream is ok if at least one track succeed.
+    // Update tracks based on its type.
+    for (size_t j = 0; j < audiotracklist->count(); ++j) {
+      scoped_refptr<MediaStreamTrackInterface> track(audiotracklist->at(j));
+      const cricket::ContentInfo* audio_content =
+          GetFirstAudioContent(answer_desc);
+      if (!audio_content) {  // The remote does not accept audio.
+        track->set_state(MediaStreamTrackInterface::kFailed);
+        continue;
+      }
+
+      const cricket::AudioContentDescription* audio_desc =
+          static_cast<const cricket::AudioContentDescription*>(
+              audio_content->description);
+      if (audio_desc->codecs().size() <= 0) {
+        // No common codec.
+        track->set_state(MediaStreamTrackInterface::kFailed);
+      }
+      track->set_state(MediaStreamTrackInterface::kLive);
+      stream_ok = true;
+    }
+
+    for (size_t j = 0; j < videotracklist->count(); ++j) {
+      scoped_refptr<MediaStreamTrackInterface> track(videotracklist->at(j));
+      const cricket::ContentInfo* video_content =
+          GetFirstVideoContent(answer_desc);
+      if (!video_content) {  // The remote does not accept video.
+        track->set_state(MediaStreamTrackInterface::kFailed);
+        continue;
+      }
+
+      const cricket::VideoContentDescription* video_desc =
+          static_cast<const cricket::VideoContentDescription*>(
+              video_content->description);
+      if (video_desc->codecs().size() <= 0) {
+        // No common codec.
+        track->set_state(MediaStreamTrackInterface::kFailed);
+      }
+      track->set_state(MediaStreamTrackInterface::kLive);
+      stream_ok = true;
+    }
+
+    if (stream_ok) {
+      // We have successfully negotiated to send this stream.
+      // Change the stream and store it as successfully negotiated.
+      stream->set_ready_state(MediaStreamInterface::kLive);
+      current_local_streams->AddStream(stream);
+    } else {
+      stream->set_ready_state(MediaStreamInterface::kEnded);
+    }
+  }
+
+  // Iterate the old list of local streams.
+  // If a stream is not found in the new list it have been removed.
+  // Change the state of the removed stream and all its tracks to kEnded.
+  for (size_t i = 0; i < local_streams_->count(); ++i) {
+    MediaStreamInterface* old_stream = local_streams_->at(i);
+    MediaStreamInterface* new_streams =
+        negotiated_streams->find(old_stream->label());
+
+    if (new_streams != NULL) continue;
+
+    old_stream->set_ready_state(MediaStreamInterface::kEnded);
+    scoped_refptr<AudioTracks> audio_tracklist(old_stream->audio_tracks());
+    for (size_t j = 0; j < audio_tracklist->count(); ++j) {
+      audio_tracklist->at(j)->set_state(MediaStreamTrackInterface::kEnded);
+    }
+    scoped_refptr<VideoTracks> video_tracklist(old_stream->video_tracks());
+    for (size_t j = 0; j < video_tracklist->count(); ++j) {
+      video_tracklist->at(j)->set_state(MediaStreamTrackInterface::kEnded);
+    }
+  }
+
+  // Update the local_streams_ for next update.
+  local_streams_ = current_local_streams;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionsignaling.h b/talk/app/webrtc/peerconnectionsignaling.h
new file mode 100644
index 0000000..9e195ee
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionsignaling.h
@@ -0,0 +1,262 @@
+/*
+ * 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.
+ */
+
+// This file contains classes used for handling signaling between
+// two PeerConnections.
+
+#ifndef TALK_APP_WEBRTC_PEERCONNECTIONSIGNALING_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONSIGNALING_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/roaperrorcodes.h"
+#include "talk/app/webrtc/roapsession.h"
+#include "talk/app/webrtc/webrtcsessionobserver.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/scoped_ref_ptr.h"
+#include "talk/base/sigslot.h"
+
+namespace cricket {
+class ChannelManager;
+class Candidate;
+typedef std::vector<Candidate> Candidates;
+}
+
+namespace talk_base {
+class Thread;
+}
+
+namespace webrtc {
+
+class SessionDescriptionProvider;
+class StreamCollection;
+class StreamCollectionInterface;
+class MediaStreamInterface;
+
+// PeerConnectionSignaling is a class responsible for handling signaling
+// between two PeerConnection objects. It creates remote MediaStream objects
+// when the remote peer signals it wants to send a new MediaStream. It changes
+// the state of local MediaStreams and tracks when a remote peer is ready to
+// receive media.
+//
+// PeerConnectionSignaling is Thread-compatible and all non-const methods are
+// expected to be called on the signaling thread.
+//
+// Note that before PeerConnectionSignaling can process an answer or create an
+// offer OnCandidatesReady has to be called. The last request to create an offer
+// or process an answer will be processed after OnCandidatesReady has been
+// called.
+//
+// Call CreateOffer to negotiate new local streams to send.
+// Call ProcessSignalingMessage when a new message has been received from the
+// remote peer. This might result in one or more signals being triggered to
+// indicate changes in the offer from the the remote peer or a detected error.
+// PeerConnectionSignaling creates Offers and Answers asynchronous on the
+// signaling thread.
+//
+// Example usage: Creating an offer with one audio track.
+//
+// class ProviderImpl : public SessionDescriptionProvider {
+//  ...
+// };
+//
+// void OnSignalingMessage(const std::string& smessage) { ... }
+//
+// ProviderImpl impl;
+// PeerConnectionSignaling pc(talk_base::Thread::Current(), &impl);
+//
+// // Connect the function OnSignalingMessage to the signal
+// // SignalNewPeerConnectionMessage.
+// pc.SignalNewPeerConnectionMessage.connect(&OnSignalingMessage);
+//
+// // Initialize PeerConnectionSignaling by providing the candidates for
+// // this session.
+// pc.OnCandidatesReady(candidates);
+// // Create an offer with one stream with one audio track.
+// AudioTrack audio;
+// MediaStream local_stream1;
+// local_stream1.AddTrack(&audio);
+// StreamCollection local_streams;
+// local_streams.AddStream(&local_stream1)
+// pc.CreateOffer(&local_streams);
+// // When the offer has been created, OnsignalingMessage is called
+// // with the offer in a string. Provide this offer to the remote
+// // PeerConnection. The remote PeerConnection will then respond with an answer
+// // string. Provide this answer string to PeerConnectionSignaling.
+// pc.ProcessSignalingMessage(remote_message, &local_streams);
+
+
+class PeerConnectionSignaling : public WebRtcSessionObserver,
+                                public talk_base::MessageHandler {
+ public:
+  enum State {
+    // Awaiting the local candidates.
+    kInitializing,
+    // Ready to sent new offer or receive a new offer.
+    kIdle,
+    // An offer has been sent and expect an answer.
+    kWaitingForAnswer,
+    // An answer have been sent and expect an ok message.
+    kWaitingForOK,
+    // SendShutdown has been called. No more messages are processed.
+    kShutingDown,
+    // Shutdown message have been received or remote peer have answered ok
+    // to a sent shutdown message.
+    kShutdownComplete,
+  };
+
+  // Constructs a PeerConnectionSignaling instance.
+  // signaling_thread - the thread where all signals will be triggered from.
+  // Also all calls to to methods are expected to be called on this thread.
+  // provider - Implementation of the SessionDescriptionProvider interface.
+  // This interface provides methods for returning local offer and answer
+  // session descriptions as well as functions for receiving events about
+  // negotiation completion and received remote session descriptions.
+  PeerConnectionSignaling(talk_base::Thread* signaling_thread,
+                          SessionDescriptionProvider* provider);
+  virtual ~PeerConnectionSignaling();
+
+  // Process a received offer/answer from the remote peer.
+  // local_streams must be the collection of streams the peerconnection
+  // currently would like to send.
+  void ProcessSignalingMessage(const std::string& message,
+                               StreamCollectionInterface* local_streams);
+
+  // Creates an offer containing all tracks in local_streams.
+  // When the offer is ready it is signaled by SignalNewPeerConnectionMessage.
+  // When the remote peer is ready to receive media on a stream , the state of
+  // the local streams will change to kLive.
+  void CreateOffer(StreamCollectionInterface* local_streams);
+
+  // Creates a ShutDown message to be sent to the remote peer.
+  // When the message is ready it is signaled by SignalNewPeerConnectionMessage.
+  // After calling this no more offers or answers to offers can be created.
+  void SendShutDown();
+
+  // Implements WebRtcSessionObserver interface.
+  // OnCandidatesReady is called when local candidates have been collected.
+  // This tell PeerConnectionSignaling that it is ready to respond to offers
+  // and create offer messages.
+  virtual void OnCandidatesReady(const cricket::Candidates& candidates);
+
+  // Returns all current remote MediaStreams.
+  StreamCollection* remote_streams() { return remote_streams_.get(); }
+
+  // Returns the current state.
+  State GetState() const { return state_; }
+
+  // A new ROAP message is ready to be sent. The listener to this signal is
+  // supposed to deliver this message to the remote peer.
+  sigslot::signal1<const std::string&> SignalNewPeerConnectionMessage;
+
+  // A new remote stream has been discovered.
+  sigslot::signal1<MediaStreamInterface*> SignalRemoteStreamAdded;
+
+  // A remote stream is no longer available.
+  sigslot::signal1<MediaStreamInterface*> SignalRemoteStreamRemoved;
+
+  // The signaling state have changed.
+  sigslot::signal1<State> SignalStateChange;
+
+  // Remote PeerConnection sent an error message.
+  sigslot::signal1<RoapErrorCode> SignalErrorMessageReceived;
+
+ private:
+  typedef std::list<talk_base::scoped_refptr<StreamCollectionInterface> >
+          StreamCollectionList;
+
+  // Implements talk_base::MessageHandler.
+  virtual void OnMessage(talk_base::Message* msg);
+
+  // Change the State and triggers the SignalStateChange signal.
+  void ChangeState(State new_state);
+
+  // Creates an offer on the signaling_thread_.
+  // This is either initiated by CreateOffer or OnCandidatesReady.
+  void CreateOffer_s();
+
+  // Creates an answer on the signaling thread.
+  // This is either initiated by ProcessSignalingMessage when a remote offer
+  // have been received or OnCandidatesReady.
+  void CreateAnswer_s();
+
+  // Notifies the provider_ and the active remote media streams
+  // about the shutdown.
+  // This is either initiated by ProcessSignalingMessage when a remote shutdown
+  // message have been received or by a call to SendShutDown.
+  void DoShutDown();
+
+  // Creates and destroys remote media streams based on remote_desc.
+  void UpdateRemoteStreams(const cricket::SessionDescription* remote_desc);
+
+  // Updates the state of local streams based on the answer_desc and the streams
+  // that have been negotiated in negotiated_streams.
+  void UpdateSendingLocalStreams(
+      const cricket::SessionDescription* answer_desc,
+      StreamCollectionInterface* negotiated_streams);
+
+  talk_base::Thread* signaling_thread_;
+  SessionDescriptionProvider* provider_;
+  State state_;
+
+  // Flag indicating PeerConnectionSignaling was called with an offer while
+  // PeerConnectionSignaling is in kInitializing state.
+  bool received_pre_offer_;
+
+  // LocalStreams queued for later use if ProcessSignalingMessage or CreateOffer
+  // is called while PeerConnectionSignaling is in kInitializing state or
+  // CreateOffer is called while PeerConnectionSignaling is currently sending
+  // an offer.
+  StreamCollectionList queued_local_streams_;
+
+  // Currently known remote MediaStreams.
+  talk_base::scoped_refptr<StreamCollection> remote_streams_;
+
+  // The local session description of the local MediaStreams that is being
+  // negotiated.
+  talk_base::scoped_ptr<const cricket::SessionDescription> local_desc_;
+
+  // Local MediaStreams being negotiated.
+  talk_base::scoped_refptr<StreamCollection> local_streams_;
+
+  // The set of local transport candidates used in negotiation.
+  // This is set by OnCandidatesReady.
+  cricket::Candidates candidates_;
+
+  // roap_session_ holds the ROAP-specific session state and is used for
+  // creating a parsing ROAP messages.
+  RoapSession roap_session_;
+
+  DISALLOW_COPY_AND_ASSIGN(PeerConnectionSignaling);
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTIONSIGNALING_H_
diff --git a/talk/app/webrtc/peerconnectionsignaling_unittest.cc b/talk/app/webrtc/peerconnectionsignaling_unittest.cc
new file mode 100644
index 0000000..8980128
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionsignaling_unittest.cc
@@ -0,0 +1,592 @@
+/*
+ * 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 <map>
+#include <string>
+#include <utility>
+
+#include "talk/app/webrtc/audiotrackimpl.h"
+#include "talk/app/webrtc/mediastreamimpl.h"
+#include "talk/app/webrtc/peerconnectionsignaling.h"
+#include "talk/app/webrtc/sessiondescriptionprovider.h"
+#include "talk/app/webrtc/streamcollectionimpl.h"
+#include "talk/app/webrtc/videotrackimpl.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/channelmanager.h"
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStreamLabel2[] = "local_stream_2";
+static const char kAudioTrackLabel1[] = "local_audio_1";
+static const char kAudioTrackLabel2[] = "local_audio_2";
+static const char kVideoTrackLabel1[] = "local_video_1";
+
+namespace webrtc {
+
+typedef std::map<std::string, talk_base::scoped_refptr<MediaStreamInterface> >
+    MediaStreamMap;
+typedef std::pair<std::string, talk_base::scoped_refptr<MediaStreamInterface> >
+    RemotePair;
+
+// MockSignalingObserver implements functions for listening all signals from a
+// PeerConnectionSignaling instance.
+// The method AnswerPeer can be used to forward messages from one
+// PeerConnectionSignaling instance to another.
+class MockSignalingObserver : public sigslot::has_slots<> {
+ public:
+  MockSignalingObserver()
+      : last_error_(-1),  // Initialize last_error_ to unused error code.
+        state_(PeerConnectionSignaling::kInitializing),
+        remote_peer_(NULL) {
+  }
+
+  virtual ~MockSignalingObserver() {}
+
+  // New remote stream have been discovered.
+  virtual void OnRemoteStreamAdded(MediaStreamInterface* remote_stream) {
+    EXPECT_EQ(MediaStreamInterface::kLive, remote_stream->ready_state());
+    remote_media_streams_.insert(RemotePair(remote_stream->label(),
+                                            remote_stream));
+  }
+
+  // Remote stream is no longer available.
+  virtual void OnRemoteStreamRemoved(MediaStreamInterface* remote_stream) {
+    EXPECT_TRUE(remote_media_streams_.find(remote_stream->label()) !=
+                remote_media_streams_.end());
+    EXPECT_EQ(MediaStreamInterface::kEnded, remote_stream->ready_state());
+    remote_media_streams_.erase(remote_stream->label());
+  }
+
+  virtual void OnStateChange(PeerConnectionSignaling::State state) {
+    state_  = state;
+  }
+
+  virtual void OnErrorReceived(RoapErrorCode error) {
+    last_error_ = error;
+  }
+
+  void OnSignalingMessage(const std::string& smessage) {
+    last_message_ = smessage;
+    if (remote_peer_) {
+      remote_peer_->ProcessSignalingMessage(smessage, remote_local_collection_);
+    }
+  }
+
+  // Tell this object to answer the remote_peer.
+  // remote_local_collection is the local collection the remote peer want to
+  // send in an answer.
+  void AnswerPeer(PeerConnectionSignaling* remote_peer,
+                  StreamCollection* remote_local_collection) {
+    remote_peer_ = remote_peer;
+    remote_local_collection_ = remote_local_collection;
+  }
+
+  void CancelAnswerPeer() {
+    remote_peer_ = NULL;
+    remote_local_collection_.release();
+  }
+
+  MediaStreamInterface* RemoteStream(const std::string& label) {
+    MediaStreamMap::iterator it = remote_media_streams_.find(label);
+    if (it != remote_media_streams_.end())
+      return it->second;
+    return NULL;
+  }
+
+  std::string last_message_;
+  int last_error_;
+  PeerConnectionSignaling::State state_;
+
+ private:
+  MediaStreamMap remote_media_streams_;
+  talk_base::scoped_refptr<StreamCollection> remote_local_collection_;
+  PeerConnectionSignaling* remote_peer_;
+};
+
+// Mock implementation of SessionDescriptionProvider.
+// PeerConnectionSignaling uses this object to create session descriptions.
+class MockSessionDescriptionProvider : public SessionDescriptionProvider {
+ public:
+  explicit MockSessionDescriptionProvider(
+      cricket::ChannelManager* channel_manager)
+      : update_session_description_counter_(0),
+        session_description_factory_(
+            new cricket::MediaSessionDescriptionFactory(channel_manager)),
+            offer_set_(false) {
+  }
+  virtual cricket::SessionDescription* CreateOffer(
+      const cricket::MediaSessionOptions& options) {
+    return session_description_factory_->CreateOffer(options,
+                                                     local_desc_.get());
+  }
+  virtual cricket::SessionDescription* CreateAnswer(
+      const cricket::SessionDescription*offer,
+      const cricket::MediaSessionOptions& options) {
+    return session_description_factory_->CreateAnswer(offer, options,
+                                                      local_desc_.get());
+  }
+  virtual void SetLocalDescription(const cricket::SessionDescription* desc,
+                                   cricket::ContentAction type) {
+    local_desc_.reset(desc);
+    UpdateNegotiationState(type);
+  }
+  virtual void SetRemoteDescription(
+      cricket::SessionDescription* remote_offer,
+      cricket::ContentAction type) {
+    remote_desc_.reset(remote_offer);
+    UpdateNegotiationState(type);
+  }
+  virtual const cricket::SessionDescription* local_description() const {
+    return local_desc_.get();
+  }
+  virtual const cricket::SessionDescription* remote_description() const {
+    return remote_desc_.get();
+  }
+  virtual void SetRemoteCandidates(
+      const std::vector<cricket::Candidate>& remote_candidates) {
+  }
+
+  // |update_session_description_counter_| is the number of successful
+  // negotiations / re-negotiations.
+  size_t update_session_description_counter_;
+
+ protected:
+  void UpdateNegotiationState(cricket::ContentAction type) {
+    if (type  == cricket::CA_ANSWER && offer_set_) {
+      // We have received and offer and now we receive an answer.
+      // Negotiation is done. Update the counter to indicate this.
+      ++update_session_description_counter_;
+      offer_set_ = false;
+    } else {
+      // Received an offer when expecting an answer.
+      EXPECT_FALSE(offer_set_);
+      offer_set_ = true;
+    }
+  }
+
+  talk_base::scoped_ptr<cricket::MediaSessionDescriptionFactory>
+      session_description_factory_;
+  talk_base::scoped_ptr<const cricket::SessionDescription> local_desc_;
+  talk_base::scoped_ptr<const cricket::SessionDescription> remote_desc_;
+  bool offer_set_;
+};
+
+// PeerConnectionSignalingTest create two PeerConnectionSignaling instances
+// and connects the signals to two MockSignalingObservers.
+// This is used in tests to test the signaling between to peers.
+class PeerConnectionSignalingTest: public testing::Test {
+ protected:
+  virtual void SetUp() {
+    channel_manager_.reset(new cricket::ChannelManager(
+        talk_base::Thread::Current()));
+    EXPECT_TRUE(channel_manager_->Init());
+    provider1_.reset(new MockSessionDescriptionProvider(
+        channel_manager_.get()));
+    provider2_.reset(new MockSessionDescriptionProvider(
+        channel_manager_.get()));
+
+    signaling1_.reset(new PeerConnectionSignaling(
+        talk_base::Thread::Current(), provider1_.get()));
+    observer1_.reset(new MockSignalingObserver());
+    signaling1_->SignalNewPeerConnectionMessage.connect(
+        observer1_.get(), &MockSignalingObserver::OnSignalingMessage);
+    signaling1_->SignalRemoteStreamAdded.connect(
+        observer1_.get(), &MockSignalingObserver::OnRemoteStreamAdded);
+    signaling1_->SignalRemoteStreamRemoved.connect(
+        observer1_.get(), &MockSignalingObserver::OnRemoteStreamRemoved);
+    signaling1_->SignalErrorMessageReceived.connect(
+        observer1_.get(), &MockSignalingObserver::OnErrorReceived);
+    signaling1_->SignalStateChange.connect(
+        observer1_.get(), &MockSignalingObserver::OnStateChange);
+
+    signaling2_.reset(new PeerConnectionSignaling(
+        talk_base::Thread::Current(), provider2_.get()));
+    observer2_.reset(new MockSignalingObserver());
+    signaling2_->SignalNewPeerConnectionMessage.connect(
+        observer2_.get(), &MockSignalingObserver::OnSignalingMessage);
+    signaling2_->SignalRemoteStreamAdded.connect(
+        observer2_.get(), &MockSignalingObserver::OnRemoteStreamAdded);
+    signaling2_->SignalRemoteStreamRemoved.connect(
+        observer2_.get(), &MockSignalingObserver::OnRemoteStreamRemoved);
+    signaling2_->SignalErrorMessageReceived.connect(
+        observer2_.get(), &MockSignalingObserver::OnErrorReceived);
+    signaling2_->SignalStateChange.connect(
+        observer2_.get(), &MockSignalingObserver::OnStateChange);
+  }
+
+  // Create a collection of streams be sent on signaling1__
+  talk_base::scoped_refptr<StreamCollection> CreateLocalCollection1() {
+    std::string label(kStreamLabel1);
+    talk_base::scoped_refptr<LocalMediaStreamInterface> stream1(
+        MediaStream::Create(label));
+
+    // Add a local audio track.
+    talk_base::scoped_refptr<LocalAudioTrackInterface>
+        audio_track(AudioTrack::CreateLocal(kAudioTrackLabel1, NULL));
+    stream1->AddTrack(audio_track);
+
+    // Add a local video track.
+    talk_base::scoped_refptr<LocalVideoTrackInterface>
+        video_track(VideoTrack::CreateLocal(kVideoTrackLabel1, NULL));
+    stream1->AddTrack(video_track);
+
+    talk_base::scoped_refptr<StreamCollection> local_collection1(
+        StreamCollection::Create());
+    local_collection1->AddStream(stream1);
+
+    return local_collection1;
+  }
+
+  talk_base::scoped_refptr<StreamCollection> CreateLocalCollection2() {
+    std::string label(kStreamLabel2);
+    talk_base::scoped_refptr<LocalMediaStreamInterface> stream2(
+        MediaStream::Create(label));
+
+    // Add a local audio track.
+    talk_base::scoped_refptr<LocalAudioTrackInterface>
+        audio_track(AudioTrack::CreateLocal(kAudioTrackLabel2, NULL));
+    stream2->AddTrack(audio_track);
+
+    talk_base::scoped_refptr<StreamCollection> local_collection2(
+        StreamCollection::Create());
+    local_collection2->AddStream(stream2);
+
+    return local_collection2;
+  }
+
+  void VerifyStreamStates(StreamCollection* collection,
+                          MediaStreamInterface::ReadyState state,
+                          MediaStreamTrackInterface::TrackState track_state) {
+    for (size_t i = 0; i < collection->count(); ++i) {
+      MediaStreamInterface* stream = collection->at(i);
+      EXPECT_EQ(state, stream->ready_state());
+      for (size_t j = 0; j < stream->audio_tracks()->count(); ++j) {
+        AudioTrackInterface* audio = stream->audio_tracks()->at(j);
+        EXPECT_EQ(track_state, audio->state());
+      }
+      for (size_t j = 0; j < stream->video_tracks()->count(); ++j) {
+        VideoTrackInterface* video = stream->video_tracks()->at(j);
+        EXPECT_EQ(track_state, video->state());
+      }
+    }
+  }
+
+  // Initialize and setup a simple call between signaling1_ and signaling2_.
+  // signaling1_ send stream with label kStreamLabel1 to signaling2_.
+  void SetUpOneWayCall() {
+    // Initialize signaling1_ and signaling_2 by providing the candidates.
+    signaling1_->OnCandidatesReady(candidates_);
+    signaling2_->OnCandidatesReady(candidates_);
+
+    // Create a local stream collection to be sent on signaling1_.
+    talk_base::scoped_refptr<StreamCollection> local_collection1(
+        CreateLocalCollection1());
+
+    talk_base::scoped_refptr<StreamCollection> local_collection2(
+        StreamCollection::Create());
+
+    // Connect all messages sent from signaling1_ to be received on signaling2_
+    observer1_->AnswerPeer(signaling2_.get(), local_collection2);
+    // Connect all messages sent from Peer2 to be received on Peer1
+    observer2_->AnswerPeer(signaling1_.get(), local_collection1);
+
+    signaling1_->CreateOffer(local_collection1);
+    EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+              signaling1_->GetState());
+    EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
+
+    // Process posted messages to generate the offer and the answer to the
+    // offer.
+    talk_base::Thread::Current()->ProcessMessages(1);
+    talk_base::Thread::Current()->ProcessMessages(1);
+
+    // Make sure all is setup.
+    EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
+    EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
+
+    EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) != NULL);
+    EXPECT_EQ(0u, signaling1_->remote_streams()->count());
+    EXPECT_EQ(1u, signaling2_->remote_streams()->count());
+  }
+
+  cricket::Candidates candidates_;
+  talk_base::scoped_ptr<MockSignalingObserver> observer1_;
+  talk_base::scoped_ptr<MockSignalingObserver> observer2_;
+  talk_base::scoped_ptr<MockSessionDescriptionProvider> provider1_;
+  talk_base::scoped_ptr<MockSessionDescriptionProvider> provider2_;
+  talk_base::scoped_ptr<PeerConnectionSignaling> signaling1_;
+  talk_base::scoped_ptr<PeerConnectionSignaling> signaling2_;
+  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+};
+
+TEST_F(PeerConnectionSignalingTest, SimpleOneWayCall) {
+  // Peer 1 create an offer with only one audio track.
+  talk_base::scoped_refptr<StreamCollection> local_collection1(
+      CreateLocalCollection1());
+
+  // Verify that the local stream is now initializing.
+  VerifyStreamStates(local_collection1.get(),
+                     MediaStreamInterface::kInitializing,
+                     MediaStreamTrackInterface::kInitializing);
+
+  // Peer 2 only receive. Create an empty collection
+  talk_base::scoped_refptr<StreamCollection> local_collection2(
+      StreamCollection::Create());
+
+  // Connect all messages sent from Peer1 to be received on Peer2
+  observer1_->AnswerPeer(signaling2_.get(), local_collection2);
+  // Connect all messages sent from Peer2 to be received on Peer1
+  observer2_->AnswerPeer(signaling1_.get(), local_collection1);
+
+  // Peer 1 generates the offer. It is not sent since there is no
+  // local candidates ready.
+  signaling1_->CreateOffer(local_collection1);
+
+  // Process posted messages.
+  talk_base::Thread::Current()->ProcessMessages(1);
+  EXPECT_EQ(PeerConnectionSignaling::kInitializing, signaling1_->GetState());
+
+  // Initialize signaling1_ by providing the candidates.
+  signaling1_->OnCandidatesReady(candidates_);
+  EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+            signaling1_->GetState());
+  // Process posted messages to allow signaling_1 to send the offer.
+  talk_base::Thread::Current()->ProcessMessages(1);
+
+  // Verify that signaling_2 is still not initialized.
+  // Even though it have received an offer.
+  EXPECT_EQ(PeerConnectionSignaling::kInitializing, signaling2_->GetState());
+
+  // Provide the candidates to signaling_2 and let it process the offer.
+  signaling2_->OnCandidatesReady(candidates_);
+  talk_base::Thread::Current()->ProcessMessages(1);
+
+  // Verify that the offer/answer have been exchanged and the state is good.
+  EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
+  EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
+
+  // Verify that the local stream is now sending.
+  VerifyStreamStates(local_collection1, MediaStreamInterface::kLive,
+                     MediaStreamTrackInterface::kLive);
+
+  // Verify that PeerConnection2 is aware of the sending stream.
+  EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) != NULL);
+
+  // Verify that both peers have updated the session descriptions.
+  EXPECT_EQ(1u, provider1_->update_session_description_counter_);
+  EXPECT_EQ(1u, provider2_->update_session_description_counter_);
+}
+
+TEST_F(PeerConnectionSignalingTest, Glare) {
+  // Setup a call.
+  SetUpOneWayCall();
+
+  // Stop sending all messages automatically between Peer 1 and Peer 2.
+  observer1_->CancelAnswerPeer();
+  observer2_->CancelAnswerPeer();
+
+  // Create an empty collection for Peer 1.
+  talk_base::scoped_refptr<StreamCollection> local_collection1(
+      StreamCollection::Create());
+  // Create a collection for Peer 2.
+  talk_base::scoped_refptr<StreamCollection> local_collection2(
+      CreateLocalCollection2());
+
+  // Peer 1 create an updated offer.
+  signaling1_->CreateOffer(local_collection1);
+  // Peer 2 create an updated offer.
+  signaling2_->CreateOffer(local_collection2);
+
+  // Process posted messages.
+  talk_base::Thread::Current()->ProcessMessages(1);
+  talk_base::Thread::Current()->ProcessMessages(1);
+
+  std::string offer_1 = observer1_->last_message_;
+  std::string offer_2 = observer2_->last_message_;
+  EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+            signaling1_->GetState());
+  EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+            signaling2_->GetState());
+
+  // Connect all messages sent from Peer 1 to be received on Peer 2
+  observer1_->AnswerPeer(signaling2_.get(), local_collection2);
+  // Connect all messages sent from Peer 2 to be received on Peer 1
+  observer2_->AnswerPeer(signaling1_.get(), local_collection1);
+
+  // Insert the two offers to each Peer to create the Glare.
+  signaling1_->ProcessSignalingMessage(offer_2, local_collection1);
+  signaling2_->ProcessSignalingMessage(offer_1, local_collection2);
+
+  talk_base::Thread::Current()->ProcessMessages(1);
+  talk_base::Thread::Current()->ProcessMessages(1);
+
+  // Make sure all is good.
+  EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
+  EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
+
+  // Verify that Peer 1 is receiving kStreamLabel2.
+  EXPECT_TRUE(observer1_->RemoteStream(kStreamLabel2) != NULL);
+  // Verify that Peer 2 don't receive any streams
+  // since it has been removed.
+  EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) == NULL);
+
+  // Verify that both peers have updated the session descriptions.
+  EXPECT_EQ(2u, provider1_->update_session_description_counter_);
+  EXPECT_EQ(2u, provider2_->update_session_description_counter_);
+}
+
+TEST_F(PeerConnectionSignalingTest, AddRemoveStream) {
+  // Initialize signaling1_ and signaling_2 by providing the candidates.
+  signaling1_->OnCandidatesReady(candidates_);
+  signaling2_->OnCandidatesReady(candidates_);
+  // Create a local stream.
+  std::string label(kStreamLabel1);
+  talk_base::scoped_refptr<LocalMediaStreamInterface> stream(
+      MediaStream::Create(label));
+
+  // Add a local audio track.
+  talk_base::scoped_refptr<LocalAudioTrackInterface>
+      audio_track(AudioTrack::CreateLocal(kAudioTrackLabel1, NULL));
+  stream->AddTrack(audio_track);
+
+  // Add a local video track.
+  talk_base::scoped_refptr<LocalVideoTrackInterface>
+      video_track(VideoTrack::CreateLocal(kVideoTrackLabel1, NULL));
+  stream->AddTrack(video_track);
+
+  // Peer 1 create an empty collection
+  talk_base::scoped_refptr<StreamCollection> local_collection1(
+      StreamCollection::Create());
+
+  // Peer 2 create an empty collection
+  talk_base::scoped_refptr<StreamCollection> local_collection2(
+      StreamCollection::Create());
+
+  // Connect all messages sent from Peer1 to be received on Peer2
+  observer1_->AnswerPeer(signaling2_.get(), local_collection2);
+  // Connect all messages sent from Peer2 to be received on Peer1
+  observer2_->AnswerPeer(signaling1_.get(), local_collection1);
+
+  // Peer 1 creates an empty offer and send it to Peer2.
+  signaling1_->CreateOffer(local_collection1);
+  // Process posted messages.
+  talk_base::Thread::Current()->ProcessMessages(1);
+  talk_base::Thread::Current()->ProcessMessages(1);
+
+  // Verify that both peers have updated the session descriptions.
+  EXPECT_EQ(1u, provider1_->update_session_description_counter_);
+  EXPECT_EQ(1u, provider2_->update_session_description_counter_);
+
+  // Peer2 add a stream.
+  local_collection2->AddStream(stream);
+
+  signaling2_->CreateOffer(local_collection2);
+  talk_base::Thread::Current()->ProcessMessages(1);
+  talk_base::Thread::Current()->ProcessMessages(1);
+
+  // Verify that the PeerConnection 2 local stream is now sending.
+  VerifyStreamStates(local_collection2, MediaStreamInterface::kLive ,
+                     MediaStreamTrackInterface::kLive);
+
+  // Verify that PeerConnection1 is aware of the sending stream.
+  EXPECT_TRUE(observer1_->RemoteStream(label) != NULL);
+
+  // Verify that both peers have updated the session descriptions.
+  EXPECT_EQ(2u, provider1_->update_session_description_counter_);
+  EXPECT_EQ(2u, provider2_->update_session_description_counter_);
+
+  // Remove the stream
+  local_collection2->RemoveStream(stream);
+
+  signaling2_->CreateOffer(local_collection2);
+  talk_base::Thread::Current()->ProcessMessages(1);
+  talk_base::Thread::Current()->ProcessMessages(1);
+
+  // Verify that PeerConnection1 is not aware of the sending stream.
+  EXPECT_TRUE(observer1_->RemoteStream(label) == NULL);
+
+  // Verify that the PeerConnection 2 local stream is now ended.
+  VerifyStreamStates(local_collection2, MediaStreamInterface::kEnded ,
+                     MediaStreamTrackInterface::kEnded);
+
+  // Verify that both peers have updated the session descriptions.
+  EXPECT_EQ(3u, provider1_->update_session_description_counter_);
+  EXPECT_EQ(3u, provider2_->update_session_description_counter_);
+}
+
+TEST_F(PeerConnectionSignalingTest, ShutDown) {
+  // Setup a call.
+  SetUpOneWayCall();
+
+  signaling1_->SendShutDown();
+
+  EXPECT_EQ_WAIT(PeerConnectionSignaling::kShutdownComplete,
+                 signaling1_->GetState(), 10);
+  EXPECT_EQ_WAIT(PeerConnectionSignaling::kShutdownComplete,
+                 signaling2_->GetState(), 10);
+
+  EXPECT_EQ(0u, signaling1_->remote_streams()->count());
+  EXPECT_EQ(0u, signaling2_->remote_streams()->count());
+  EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) == NULL);
+  EXPECT_EQ(PeerConnectionSignaling::kShutdownComplete, observer1_->state_);
+  EXPECT_EQ(PeerConnectionSignaling::kShutdownComplete, observer2_->state_);
+
+  // Verify that both peers have updated the session descriptions.
+  EXPECT_EQ(2u, provider1_->update_session_description_counter_);
+  EXPECT_EQ(2u, provider2_->update_session_description_counter_);
+}
+
+TEST_F(PeerConnectionSignalingTest, ReceiveError) {
+  // Initialize signaling1_
+  signaling1_->OnCandidatesReady(candidates_);
+
+  talk_base::scoped_refptr<StreamCollection> local_collection1(
+      CreateLocalCollection1());
+
+  signaling1_->CreateOffer(local_collection1);
+  talk_base::Thread::Current()->ProcessMessages(1);
+  EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+            signaling1_->GetState());
+
+  RoapSession roap_session;
+  roap_session.Parse(observer1_->last_message_);
+  signaling1_->ProcessSignalingMessage(roap_session.CreateErrorMessage(
+      kNoMatch), local_collection1);
+  EXPECT_EQ(kNoMatch, observer1_->last_error_);
+
+  // Check signaling have cleaned up.
+  EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
+
+  signaling1_->CreateOffer(local_collection1);
+  talk_base::Thread::Current()->ProcessMessages(1);
+  EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+            signaling1_->GetState());
+}
+
+}  // namespace webrtc
+
diff --git a/talk/app/webrtc/portallocatorfactory.cc b/talk/app/webrtc/portallocatorfactory.cc
new file mode 100644
index 0000000..e997216
--- /dev/null
+++ b/talk/app/webrtc/portallocatorfactory.cc
@@ -0,0 +1,95 @@
+/*
+ * 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/portallocatorfactory.h"
+
+#include "talk/base/logging.h"
+#include "talk/base/network.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/client/httpportallocator.h"
+
+static const char kUserAgent[] = "PeerConnection User Agent";
+
+namespace webrtc {
+
+using talk_base::scoped_ptr;
+
+talk_base::scoped_refptr<PortAllocatorFactoryInterface>
+PortAllocatorFactory::Create(
+    talk_base::Thread* worker_thread) {
+  talk_base::RefCountedObject<PortAllocatorFactory>* allocator =
+        new talk_base::RefCountedObject<PortAllocatorFactory>(worker_thread);
+  return allocator;
+}
+
+PortAllocatorFactory::PortAllocatorFactory(talk_base::Thread* worker_thread)
+    : network_manager_(new talk_base::BasicNetworkManager()),
+      socket_factory_(new talk_base::BasicPacketSocketFactory(worker_thread)) {
+}
+
+PortAllocatorFactory::~PortAllocatorFactory() {}
+
+cricket::PortAllocator* PortAllocatorFactory::CreatePortAllocator(
+    const std::vector<StunConfiguration>& stun,
+    const std::vector<TurnConfiguration>& turn) {
+
+  scoped_ptr<cricket::HttpPortAllocator> allocator(
+      new cricket::HttpPortAllocator(
+          network_manager_.get(), socket_factory_.get(), kUserAgent));
+
+  std::vector<talk_base::SocketAddress> stun_hosts;
+  typedef std::vector<StunConfiguration>::const_iterator StunIt;
+  for (StunIt stun_it = stun.begin(); stun_it != stun.end(); ++stun_it) {
+    stun_hosts.push_back(stun_it->server);
+  }
+  allocator->SetStunHosts(stun_hosts);
+
+  if (turn.size() > 0)
+    LOG(LS_INFO) << "Not using turn server params";
+
+  // TODO - Enable TURN support once WebRtcSession can handle
+  // relay candidates.
+#if 0
+  std::vector<std::string> relay_hosts;
+  typedef std::vector<TurnConfiguration>::const_iterator TurnIt;
+  for (TurnIt turn_it = turn.begin(); turn_it != turn.end(); ++turn_it) {
+    relay_hosts.push_back(turn_it->server.hostname());
+  }
+  allocator->SetRelayHosts(relay_hosts);
+
+  // Currently we can only set the password of one relay server.
+  // Use the password of the first server. User name can currently not be set.
+  // TODO: See above limitations.
+  if (turn.size() > 0)
+    allocator->SetRelayToken(turn[0].password);
+#endif
+
+  return allocator.release();
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/portallocatorfactory.h b/talk/app/webrtc/portallocatorfactory.h
new file mode 100644
index 0000000..1b0d753
--- /dev/null
+++ b/talk/app/webrtc/portallocatorfactory.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+// This file defines the default implementation of
+// PortAllocatorFactoryInterface.
+// This implementation creates instances of cricket::HTTPPortAllocator and uses
+// the BasicNetworkManager and BasicPacketSocketFactory.
+
+#ifndef TALK_APP_WEBRTC_PORTALLOCATORFACTORY_H_
+#define TALK_APP_WEBRTC_PORTALLOCATORFACTORY_H_
+
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace cricket {
+class PortAllocator;
+}
+
+namespace talk_base {
+class BasicNetworkManager;
+class BasicPacketSocketFactory;
+}
+
+namespace webrtc {
+
+class PortAllocatorFactory : public PortAllocatorFactoryInterface {
+ public:
+  static talk_base::scoped_refptr<PortAllocatorFactoryInterface> Create(
+      talk_base::Thread* worker_thread);
+
+  virtual cricket::PortAllocator* CreatePortAllocator(
+      const std::vector<StunConfiguration>& stun,
+      const std::vector<TurnConfiguration>& turn);
+
+ protected:
+  explicit PortAllocatorFactory(talk_base::Thread* worker_thread);
+  ~PortAllocatorFactory();
+
+ private:
+  talk_base::scoped_ptr<talk_base::BasicNetworkManager> network_manager_;
+  talk_base::scoped_ptr<talk_base::BasicPacketSocketFactory> socket_factory_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PORTALLOCATORFACTORY_H_
diff --git a/talk/app/webrtc/roaperrorcodes.h b/talk/app/webrtc/roaperrorcodes.h
new file mode 100644
index 0000000..d13230d
--- /dev/null
+++ b/talk/app/webrtc/roaperrorcodes.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+// This file contain an enum of possible error codes used in Roap.
+// The meaning of the error codes are defined in
+// http://tools.ietf.org/html/draft-jennings-rtcweb-signaling-01.
+
+#ifndef TALK_APP_WEBRTC_ROAPERRORCODES_H_
+#define TALK_APP_WEBRTC_ROAPERRORCODES_H_
+
+namespace webrtc {
+
+enum RoapErrorCode {
+  kNoMatch,
+  kTimeout,
+  kRefused,
+  kConflict,
+  kDoubleConflict,
+  kFailed
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_ROAPERRORCODES_H_
diff --git a/talk/app/webrtc/roapmessages.cc b/talk/app/webrtc/roapmessages.cc
new file mode 100644
index 0000000..e0528a7
--- /dev/null
+++ b/talk/app/webrtc/roapmessages.cc
@@ -0,0 +1,293 @@
+/*
+ * 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/app/webrtc/roapmessages.h"
+
+#include "talk/app/webrtc/webrtcsdp.h"
+#include "talk/base/json.h"
+
+namespace webrtc {
+
+using cricket::Candidate;
+using cricket::SessionDescription;
+
+// ROAP message types. Must match the enum RoapMessageType.
+static const char* kMessageTypes[] = {
+  "OFFER",
+  "ANSWER",
+  "OK",
+  "SHUTDOWN",
+  "ERROR",
+};
+
+// ROAP error messages. Must match the enum RoapErrorCode.
+static const char* kErrorMessages[] = {
+  "NOMATCH",
+  "TIMEOUT",
+  "REFUSED",
+  "CONFLICT",
+  "DOUBLECONFLICT",
+  "FAILED",
+};
+
+// ROAP json keys.
+static const char kOffererSessionId[] = "offererSessionId";
+static const char kAnswererSessionId[] = "answererSessionId";
+static const char kSetSessionToken[] = "setSessionToken";
+static const char kSetResponseToken[] = "setResponseToken";
+static const char kResponseToken[] = "responseToken";
+static const char kSessionToken[] = "sessionToken";
+static const char kMessageType[] = "messageType";
+static const char kSequenceNumber[] = "seq";
+static const char kSessionDescription[] = "sdp";
+static const char kErrorType[] = "errorType";
+static const char kTieBreaker[] = "tieBreaker";
+static const char kMoreComing[] = "moreComing";
+
+RoapMessageBase::RoapMessageBase() : type_(kInvalid), seq_(0) {
+}
+
+RoapMessageBase::RoapMessageBase(RoapMessageType type,
+                                 const std::string& offer_session_id,
+                                 const std::string& answer_session_id,
+                                 const std::string& session_token,
+                                 const std::string& response_token,
+                                 uint32 seq)
+    : type_(type),
+      offer_session_id_(offer_session_id),
+      answer_session_id_(answer_session_id),
+      session_token_(session_token),
+      response_token_(response_token),
+      seq_(seq) {
+}
+
+bool RoapMessageBase::Parse(const std::string& message) {
+  Json::Reader reader;
+  if (!reader.parse(message, jmessage_))
+    return false;
+
+  std::string message_type;
+  GetStringFromJsonObject(jmessage_, kMessageType, &message_type);
+  if (message_type.empty())
+    return false;
+  bool valid_message_type = false;
+  for (int i = 0; i < kInvalid; i++) {
+    if (message_type == kMessageTypes[i]) {
+      type_ = static_cast<RoapMessageType>(i);
+      valid_message_type = true;
+      break;
+    }
+  }
+  if (!valid_message_type)
+    return false;
+
+  if (!GetStringFromJsonObject(jmessage_, kOffererSessionId,
+                               &offer_session_id_) ||
+                               offer_session_id_.empty()) {
+    // Parse offererSessionId. Allow error messages to not have an
+    // offererSessionId.
+    if (type_ != kError)
+      return false;
+  }
+
+  // answererSessionId does not necessarily need to exist in MessageBase.
+  GetStringFromJsonObject(jmessage_, kAnswererSessionId, &answer_session_id_);
+  // setSessionToken and setResponseToken is not required.
+  GetStringFromJsonObject(jmessage_, kSetSessionToken, &session_token_);
+  GetStringFromJsonObject(jmessage_, kSetResponseToken, &response_token_);
+
+  unsigned int temp_seq;
+  if (!GetUIntFromJsonObject(jmessage_, kSequenceNumber, &temp_seq)) {
+    return false;
+  }
+  if (temp_seq > 0xFFFFFFFF)
+    return false;
+  seq_ = static_cast<uint32>(temp_seq);
+
+  return true;
+}
+
+std::string RoapMessageBase::Serialize() {
+  Json::Value message;
+  SerializeElement(&message);
+  Json::StyledWriter writer;
+  return writer.write(message);
+}
+
+void RoapMessageBase::SerializeElement(Json::Value* message) {
+  ASSERT(message != NULL);
+  (*message)[kMessageType] = kMessageTypes[type_];
+  (*message)[kOffererSessionId] = offer_session_id_;
+  if (!answer_session_id_.empty())
+    (*message)[kAnswererSessionId] = answer_session_id_;
+  if (!session_token_.empty())
+    (*message)[kSessionToken] = session_token_;
+  if (!response_token_.empty())
+    (*message)[kResponseToken] = response_token_;
+  (*message)[kSequenceNumber] = seq_;
+}
+
+RoapOffer::RoapOffer(const std::string& offer_session_id,
+                     const std::string& answer_session_id,
+                     const std::string& session_token,
+                     uint32 seq,
+                     uint32 tie_breaker,
+                     const SessionDescription* desc,
+                     const std::vector<cricket::Candidate>& candidates)
+    : RoapMessageBase(kOffer, offer_session_id, answer_session_id,
+                      session_token, "", seq),
+      tie_breaker_(tie_breaker),
+      desc_(desc),
+      candidates_(candidates) {
+}
+
+RoapOffer::RoapOffer(const RoapMessageBase& base)
+    : RoapMessageBase(base),
+      desc_(NULL) {}
+
+bool RoapOffer::Parse() {
+  if (!GetUIntFromJsonObject(jmessage_, kTieBreaker, &tie_breaker_)) {
+    return false;
+  }
+
+  std::string sdp_message;
+  if (!GetStringFromJsonObject(jmessage_, kSessionDescription, &sdp_message))
+      return false;
+
+  parsed_desc_.reset(new cricket::SessionDescription());
+  return SdpDeserialize(sdp_message, parsed_desc_.get(),
+                        &candidates_);
+}
+
+void RoapOffer::SerializeElement(Json::Value* message) {
+  ASSERT(message != NULL);
+  RoapMessageBase::SerializeElement(message);
+  (*message)[kTieBreaker] = tie_breaker_;
+  (*message)[kSessionDescription] = SdpSerialize(*desc_, candidates_);
+}
+
+RoapAnswer::RoapAnswer(const std::string& offer_session_id,
+                       const std::string& answer_session_id,
+                       const std::string& session_token,
+                       const std::string& response_token,
+                       uint32 seq,
+                       const SessionDescription* desc,
+                       const std::vector<Candidate>& candidates)
+    : RoapMessageBase(kAnswer, offer_session_id, answer_session_id,
+                      session_token, response_token, seq),
+      desc_(desc),
+      candidates_(candidates) {
+}
+
+RoapAnswer::RoapAnswer(const RoapMessageBase& base)
+    : RoapMessageBase(base),
+      more_coming_(false),
+      desc_(NULL) {}
+
+bool RoapAnswer::Parse() {
+  std::string more;
+  if (GetStringFromJsonObject(jmessage_, kMoreComing, &more) && more == "true")
+    more_coming_ = true;
+
+  std::string sdp_message;
+  if (!GetStringFromJsonObject(jmessage_, kSessionDescription, &sdp_message))
+      return false;
+
+  parsed_desc_.reset(new cricket::SessionDescription());
+  return SdpDeserialize(sdp_message, parsed_desc_.get(), &candidates_);
+}
+
+void RoapAnswer::SerializeElement(Json::Value* message) {
+  ASSERT(message != NULL);
+  RoapMessageBase::SerializeElement(message);
+
+  (*message)[kSessionDescription] = SdpSerialize(*desc_, candidates_);
+}
+
+RoapError::RoapError(const RoapMessageBase& base)
+    : RoapMessageBase(base), error_(kFailed) {
+}
+
+RoapError::RoapError(const std::string& offer_session_id,
+                     const std::string& answer_session_id,
+                     const std::string& session_token,
+                     const std::string& response_token,
+                     uint32 seq,
+                     RoapErrorCode error)
+    : RoapMessageBase(kError, offer_session_id, answer_session_id,
+                      session_token, response_token, seq),
+      error_(error) {
+}
+
+bool RoapError::Parse() {
+  std::string error_string;
+  GetStringFromJsonObject(jmessage_, kErrorType, &error_string);
+  if (error_string.empty())
+    return false;
+  for (int i = 0; i < ARRAY_SIZE(kErrorMessages); i++) {
+    if (error_string == kErrorMessages[i]) {
+      error_ = static_cast<RoapErrorCode>(i);
+      return true;
+    }
+  }
+  return false;
+}
+
+void RoapError::SerializeElement(Json::Value* message) {
+  ASSERT(message != NULL);
+  ASSERT(error_< ARRAY_SIZE(kErrorMessages));
+  RoapMessageBase::SerializeElement(message);
+
+  (*message)[kErrorType] = kErrorMessages[error_];
+}
+
+RoapOk::RoapOk(const RoapMessageBase& base)
+    : RoapMessageBase(base) {
+}
+
+RoapOk::RoapOk(const std::string& offer_session_id,
+               const std::string& answer_session_id,
+               const std::string& session_token,
+               const std::string& response_token,
+               uint32 seq)
+    : RoapMessageBase(kOk, offer_session_id, answer_session_id, session_token,
+                      response_token, seq) {
+}
+
+RoapShutdown::RoapShutdown(const RoapMessageBase& base)
+    : RoapMessageBase(base) {
+}
+
+RoapShutdown::RoapShutdown(const std::string& offer_session_id,
+                                 const std::string& answer_session_id,
+                                 const std::string& session_token,
+                                 uint32 seq)
+    : RoapMessageBase(kShutdown, offer_session_id, answer_session_id,
+                      session_token, "", seq) {
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/roapmessages.h b/talk/app/webrtc/roapmessages.h
new file mode 100644
index 0000000..7b18010
--- /dev/null
+++ b/talk/app/webrtc/roapmessages.h
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+
+// This file contain classes for parsing and serializing ROAP messages.
+// The ROAP messages are defined in
+// http://tools.ietf.org/html/draft-jennings-rtcweb-signaling-01.
+
+#ifndef TALK_APP_WEBRTC_ROAPMESSAGES_H_
+#define TALK_APP_WEBRTC_ROAPMESSAGES_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/roaperrorcodes.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/json.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace webrtc {
+
+class RoapMessageBase {
+ public:
+  enum RoapMessageType {
+    kOffer = 0,
+    kAnswer = 1,
+    kOk = 2,
+    kShutdown = 3,
+    kError = 4,
+    kInvalid = 5,
+  };
+  RoapMessageBase();
+  RoapMessageBase(RoapMessageType type,
+                  const std::string& offer_session_id,
+                  const std::string& answer_session_id,
+                  const std::string& session_token,
+                  const std::string& response_token,
+                  uint32 seq);
+
+  bool Parse(const std::string& message);
+  std::string Serialize();
+
+  RoapMessageType type() const { return type_; }
+  const std::string& offer_session_id() const { return offer_session_id_; }
+  const std::string& answer_session_id() const { return answer_session_id_; }
+  const std::string& session_token() const { return session_token_; }
+  const std::string& response_token() const { return response_token_; }
+  uint32 seq() const { return seq_; }
+
+ protected:
+  virtual void SerializeElement(Json::Value* message);
+  Json::Value jmessage_;  // Contains the parsed json message.
+
+ private:
+  RoapMessageType type_;
+  std::string offer_session_id_;
+  std::string answer_session_id_;
+  std::string session_token_;
+  std::string response_token_;
+  uint32 seq_;
+};
+
+class RoapAnswer : public RoapMessageBase {
+ public:
+  explicit RoapAnswer(const RoapMessageBase& base);
+  // Note that the SessionDescription desc is used as a weak reference.
+  // The user of this class must ensure that desc outlives an instance of this
+  // object.
+  RoapAnswer(const std::string& offer_session_id,
+             const std::string& answer_session_id,
+             const std::string& session_token,
+             const std::string& response_token,
+             uint32 seq,
+             const cricket::SessionDescription* desc,
+             const std::vector<cricket::Candidate>& candidates);
+  bool Parse();
+
+  // Get remote SessionDescription if the session description has been parsed
+  // and the ownership is transferred to the caller.
+  // NULL otherwise.
+  cricket::SessionDescription* ReleaseSessionDescription() {
+    return parsed_desc_.release();
+  }
+  const std::vector<cricket::Candidate>& candidates() const {
+    return candidates_;
+  }
+  bool more_coming() const { return more_coming_ ; }
+
+ protected:
+  virtual void SerializeElement(Json::Value* message);
+
+ private:
+  bool more_coming_;
+  // Session description parsed in an offer.
+  talk_base::scoped_ptr<cricket::SessionDescription> parsed_desc_;
+  // Weak ref to a session description provided in the constructor.
+  const cricket::SessionDescription* desc_;
+  std::vector<cricket::Candidate> candidates_;
+};
+
+class RoapOffer : public RoapMessageBase {
+ public:
+  explicit RoapOffer(const RoapMessageBase& base);
+  // Note that the SessionDescription desc is used as a weak reference.
+  // The user of this class must ensure that desc outlives an instance of this
+  // object.
+  RoapOffer(const std::string& offer_session_id,
+            const std::string& answer_session_id,
+            const std::string& session_token,
+            uint32 seq,
+            uint32 tie_breaker,
+            const cricket::SessionDescription* desc,
+            const std::vector<cricket::Candidate>& candidates);
+  bool Parse();
+
+  uint32 tie_breaker() const { return tie_breaker_; }
+  // Get remote SessionDescription if the session description has been parsed
+  // and the ownership is transferred to the caller.
+  // NULL otherwise.
+  cricket::SessionDescription* ReleaseSessionDescription() {
+    return parsed_desc_.release();
+  }
+  const std::vector<cricket::Candidate>&  candidates() { return candidates_; }
+
+ protected:
+  virtual void SerializeElement(Json::Value* message);
+
+ private:
+  uint32 tie_breaker_;
+  // Session description parsed in an offer.
+  talk_base::scoped_ptr<cricket::SessionDescription> parsed_desc_;
+  // Weak reference to a session description provided in the constructor.
+  const cricket::SessionDescription* desc_;
+  std::vector<cricket::Candidate> candidates_;
+};
+
+class RoapError : public RoapMessageBase {
+ public:
+  explicit RoapError(const RoapMessageBase& base);
+  RoapError(const std::string& offer_session_id,
+            const std::string& answer_session_id,
+            const std::string& session_token,
+            const std::string& response_token,
+            uint32 seq,
+            RoapErrorCode error);
+  bool Parse();
+  RoapErrorCode error() const { return error_; }
+
+ protected:
+  virtual void SerializeElement(Json::Value* message);
+
+ private:
+  RoapErrorCode error_;
+};
+
+class RoapOk : public RoapMessageBase {
+ public:
+  explicit RoapOk(const RoapMessageBase& base);
+  RoapOk(const std::string& offer_session_id,
+         const std::string& answer_session_id,
+         const std::string& session_token,
+         const std::string& response_token,
+         uint32 seq);
+};
+
+class RoapShutdown : public RoapMessageBase {
+ public:
+  explicit RoapShutdown(const RoapMessageBase& base);
+  RoapShutdown(const std::string& offer_session_id,
+               const std::string& answer_session_id,
+               const std::string& session_token,
+               uint32 seq);
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_ROAPMESSAGES_H_
diff --git a/talk/app/webrtc/roapmessages_unittest.cc b/talk/app/webrtc/roapmessages_unittest.cc
new file mode 100644
index 0000000..b432667
--- /dev/null
+++ b/talk/app/webrtc/roapmessages_unittest.cc
@@ -0,0 +1,227 @@
+/*
+ * 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 <string>
+
+#include "talk/app/webrtc/roapmessages.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/session/phone/mediasession.h"
+
+using cricket::Candidates;
+using cricket::AudioContentDescription;
+using cricket::SessionDescription;
+using cricket::StreamParams;
+using cricket::VideoContentDescription;
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStream1Cname[] = "stream_1_cname";
+static const char kAudioTrackLabel1[] = "local_audio_1";
+static const uint32 kAudioTrack1Ssrc = 1;
+
+static const char kOfferSessionId[] = "offer_1";
+static const char kAnswerSessionId[] = "answer_1";
+static const char kSessionToken[] = "session_1";
+
+static const char kOfferReference[] =
+    "{\n"
+    "   \"answererSessionId\" : \"answer_1\",\n"
+    "   \"messageType\" : \"OFFER\",\n"
+    "   \"offererSessionId\" : \"offer_1\",\n"
+    "   \"sdp\" : \"v=0\\r\\n"
+    "o=- 0 0 IN IP4 127.0.0.1\\r\\n"
+    "s=\\r\\n"
+    "t=0 0\\r\\n"
+    "m=audio 0 RTP/AVPF\\r\\n"
+    "a=mid:audio\\r\\n"
+    "a=rtcp-mux\\r\\n"
+    "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 "
+        "label:local_audio_1\\r\\n"
+    "\",\n"  // End of sdp.
+    "   \"seq\" : 1,\n"
+    "   \"tieBreaker\" : 0\n"
+    "}\n";
+
+static const char kAnswerReference[] =
+    "{\n"
+    "   \"answererSessionId\" : \"answer_1\",\n"
+    "   \"messageType\" : \"ANSWER\",\n"
+    "   \"offererSessionId\" : \"offer_1\",\n"
+    "   \"sdp\" : \"v=0\\r\\n"
+    "o=- 0 0 IN IP4 127.0.0.1\\r\\n"
+    "s=\\r\\n"
+    "t=0 0\\r\\n"
+    "m=audio 0 RTP/AVPF\\r\\n"
+    "a=mid:audio\\r\\n"
+    "a=rtcp-mux\\r\\n"
+    "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 "
+        "label:local_audio_1\\r\\n"
+    "\",\n"  // End of sdp.
+    "   \"seq\" : 1\n"
+    "}\n";
+
+static const char kOkReference[] =
+    "{\n"
+    "   \"answererSessionId\" : \"answer_1\",\n"
+    "   \"messageType\" : \"OK\",\n"
+    "   \"offererSessionId\" : \"offer_1\",\n"
+    "   \"seq\" : 1\n"
+    "}\n";
+
+static const char kShutdownReference[] =
+    "{\n"
+    "   \"answererSessionId\" : \"answer_1\",\n"
+    "   \"messageType\" : \"SHUTDOWN\",\n"
+    "   \"offererSessionId\" : \"offer_1\",\n"
+    "   \"seq\" : 1\n"
+    "}\n";
+
+static const char kErrorReference[] =
+    "{\n"
+    "   \"answererSessionId\" : \"answer_1\",\n"
+    "   \"errorType\" : \"TIMEOUT\",\n"
+    "   \"messageType\" : \"ERROR\",\n"
+    "   \"offererSessionId\" : \"offer_1\",\n"
+    "   \"seq\" : 1\n"
+    "}\n";
+
+// RoapMessageTest creates a session description that matches the
+// reference messages above.
+class RoapMessageTest: public testing::Test {
+ public:
+  void SetUp() {
+    talk_base::scoped_ptr<AudioContentDescription> audio(
+        new AudioContentDescription());
+    audio->set_rtcp_mux(true);
+    StreamParams audio_stream;
+    audio_stream.name = kAudioTrackLabel1;
+    audio_stream.cname = kStream1Cname;
+    audio_stream.sync_label = kStreamLabel1;
+    audio_stream.ssrcs.push_back(kAudioTrack1Ssrc);
+    audio->AddStream(audio_stream);
+    desc1_.AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+                      audio.release());
+  }
+   protected:
+    cricket::SessionDescription desc1_;
+    cricket::Candidates empty_candidates_;
+};
+
+static bool CompareRoapBase(const webrtc::RoapMessageBase& base1,
+                            const webrtc::RoapMessageBase& base2) {
+  return base1.type() == base2.type() &&
+      base1.offer_session_id() == base2.offer_session_id() &&
+      base1.answer_session_id() == base2.answer_session_id() &&
+      base1.session_token() == base2.session_token() &&
+      base1.response_token() == base2.response_token() &&
+      base1.seq() == base2.seq();
+}
+
+static bool CompareRoapOffer(const webrtc::RoapOffer& offer1,
+                             const webrtc::RoapOffer& offer2) {
+  return CompareRoapBase(offer1, offer2) &&
+      offer1.tie_breaker() == offer2.tie_breaker();
+}
+
+static bool CompareRoapAnswer(const webrtc::RoapAnswer& answer1,
+                              const webrtc::RoapAnswer& answer2) {
+  return CompareRoapBase(answer1, answer2) &&
+      answer1.more_coming() == answer1.more_coming();
+}
+
+static bool CompareRoapError(const webrtc::RoapError& error1,
+                             const webrtc::RoapError& error2) {
+  return CompareRoapBase(error1, error2) &&
+      error1.error() == error2.error();
+}
+
+TEST_F(RoapMessageTest, RoapOffer) {
+  webrtc::RoapOffer offer(kOfferSessionId, kAnswerSessionId, "", 1, 0, &desc1_,
+                          empty_candidates_);
+  std::string offer_string = offer.Serialize();
+  EXPECT_TRUE(kOfferReference == offer_string);
+
+  webrtc::RoapMessageBase base;
+  EXPECT_TRUE(base.Parse(kOfferReference));
+  EXPECT_EQ(webrtc::RoapMessageBase::kOffer, base.type());
+  webrtc::RoapOffer parsed_offer(base);
+  EXPECT_TRUE(parsed_offer.Parse());
+  EXPECT_TRUE(CompareRoapOffer(offer, parsed_offer));
+}
+
+TEST_F(RoapMessageTest, RoapAnswer) {
+  webrtc::RoapAnswer answer(kOfferSessionId, kAnswerSessionId, "", "", 1,
+                            &desc1_, empty_candidates_);
+  std::string answer_string = answer.Serialize();
+  EXPECT_TRUE(kAnswerReference == answer_string);
+
+  webrtc::RoapMessageBase base;
+  EXPECT_TRUE(base.Parse(kAnswerReference));
+  EXPECT_EQ(webrtc::RoapMessageBase::kAnswer, base.type());
+  webrtc::RoapAnswer parsed_answer(base);
+  EXPECT_TRUE(parsed_answer.Parse());
+  EXPECT_TRUE(CompareRoapAnswer(answer, parsed_answer));
+}
+
+TEST_F(RoapMessageTest, RoapOk) {
+  webrtc::RoapOk ok(kOfferSessionId, kAnswerSessionId, "", "", 1);
+  std::string ok_string = ok.Serialize();
+  EXPECT_TRUE(kOkReference == ok_string);
+
+  webrtc::RoapMessageBase base;
+  EXPECT_TRUE(base.Parse(kOkReference));
+  EXPECT_EQ(webrtc::RoapMessageBase::kOk, base.type());
+  webrtc::RoapOk parsed_ok(base);
+  EXPECT_TRUE(CompareRoapBase(ok, parsed_ok));
+}
+
+TEST_F(RoapMessageTest, RoapShutdown) {
+  webrtc::RoapShutdown shutdown(kOfferSessionId, kAnswerSessionId, "", 1);
+  std::string shutdown_string = shutdown.Serialize();
+  EXPECT_TRUE(kShutdownReference == shutdown_string);
+
+  webrtc::RoapMessageBase base;
+  EXPECT_TRUE(base.Parse(kShutdownReference));
+  EXPECT_EQ(webrtc::RoapMessageBase::kShutdown, base.type());
+  webrtc::RoapShutdown parsed_shutdown(base);
+  EXPECT_TRUE(CompareRoapBase(shutdown, parsed_shutdown));
+}
+
+TEST_F(RoapMessageTest, RoapError) {
+  webrtc::RoapError error(kOfferSessionId, kAnswerSessionId, "", "", 1,
+                          webrtc::kTimeout);
+  std::string error_string = error.Serialize();
+  EXPECT_TRUE(kErrorReference == error_string);
+
+  webrtc::RoapMessageBase base;
+  EXPECT_TRUE(base.Parse(kErrorReference));
+  EXPECT_EQ(webrtc::RoapMessageBase::kError, base.type());
+  webrtc::RoapError parsed_error(base);
+  EXPECT_TRUE(parsed_error.Parse());
+  EXPECT_TRUE(CompareRoapError(error, parsed_error));
+}
diff --git a/talk/app/webrtc/roapsession.cc b/talk/app/webrtc/roapsession.cc
new file mode 100644
index 0000000..014539f
--- /dev/null
+++ b/talk/app/webrtc/roapsession.cc
@@ -0,0 +1,286 @@
+/*
+ * 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/app/webrtc/roapsession.h"
+
+#include "talk/app/webrtc/roapmessages.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+
+namespace webrtc {
+
+static const uint32 kMaxTieBreaker = 0xFFFFFFFE;
+
+static std::string CreateLocalId(const std::string& remote_id) {
+  std::string local_id;
+  do {
+    talk_base::CreateRandomString(32, &local_id);
+    ASSERT(!local_id.empty());
+  } while (local_id == remote_id);
+  return local_id;
+}
+
+RoapSession::RoapSession()
+  : seq_(0),
+    waiting_for_answer_(false),
+    received_seq_(0) {
+}
+
+std::string RoapSession::CreateOffer(
+    const SessionDescription* desc,
+    const std::vector<Candidate>& candidates) {
+  if (local_id_.empty()) {
+    local_id_ = CreateLocalId(remote_id_);
+  }
+
+  do {
+    local_tie_breaker_ = talk_base::CreateRandomNonZeroId();
+  } while (local_tie_breaker_ > kMaxTieBreaker);
+
+  RoapOffer offer(local_id_, remote_id_, session_token_, ++seq_,
+                  local_tie_breaker_, desc, candidates);
+  waiting_for_answer_ = true;
+  return offer.Serialize();
+}
+
+std::string RoapSession::CreateAnswer(
+    const SessionDescription* desc,
+    const std::vector<Candidate>& candidates) {
+  ASSERT(!remote_id_.empty());
+  if (local_id_.empty()) {
+    local_id_ = CreateLocalId(remote_id_);
+  }
+
+  RoapAnswer answer(remote_id_, local_id_, session_token_, response_token_,
+                    seq_, desc, candidates);
+  response_token_.clear();
+  return answer.Serialize();
+}
+
+std::string RoapSession::CreateOk() {
+  ASSERT(!remote_id_.empty());
+
+  if (local_id_.empty()) {
+    local_id_ = CreateLocalId(remote_id_);
+  }
+  RoapOk ok(remote_id_, local_id_, session_token_, response_token_, seq_);
+  response_token_.clear();
+  return ok.Serialize();
+}
+
+std::string RoapSession::CreateShutDown() {
+  if (local_id_.empty()) {
+    local_id_ = CreateLocalId(remote_id_);
+  }
+  RoapShutdown shutdown(local_id_, remote_id_, session_token_, ++seq_);
+  return shutdown.Serialize();
+}
+
+std::string RoapSession::CreateErrorMessage(RoapErrorCode error) {
+  if (local_id_.empty()) {
+    local_id_ = CreateLocalId(remote_id_);
+  }
+
+  RoapError message(received_offer_id_, local_id_, session_token_,
+                    response_token_, received_seq_, error);
+  response_token_.clear();
+  return message.Serialize();
+}
+
+RoapSession::ParseResult RoapSession::Parse(
+    const std::string& msg) {
+  RoapMessageBase message;
+  if (!message.Parse(msg)) {
+    LOG(LS_ERROR) << "Parse failed. Invalid Roap message?";
+    return kInvalidMessage;
+  }
+
+  received_offer_id_ = message.offer_session_id();
+  received_answer_id_ = message.answer_session_id();
+  received_seq_ = message.seq();
+  session_token_ = message.session_token();
+  response_token_ = message.response_token();
+  ParseResult result = kInvalidMessage;
+
+  switch (message.type()) {
+    case RoapMessageBase::kOffer: {
+      RoapOffer offer(message);
+      if (!offer.Parse()) {
+        LOG(LS_ERROR) << "Parse failed. Invalid Offer message?";
+        return kInvalidMessage;
+      }
+      result = ValidateOffer(&offer);
+      break;
+    }
+    case RoapMessageBase::kAnswer: {
+      RoapAnswer answer(message);
+      if (!answer.Parse()) {
+        LOG(LS_ERROR) << "Parse failed. Invalid Answer message?";
+        result = kInvalidMessage;
+      } else {
+        result = ValidateAnswer(&answer);
+      }
+      break;
+    }
+    case RoapMessageBase::kOk: {
+      result =  ValidateOk(message);
+      break;
+    }
+    case RoapMessageBase::kShutdown: {
+      // Always accept shutdown messages.
+      if (remote_id_.empty()) {
+        remote_id_ = message.offer_session_id();
+      }
+      seq_ = message.seq();
+      result = kShutDown;
+      break;
+    }
+    case RoapMessageBase::kError: {
+      RoapError error(message);
+      if (!error.Parse()) {
+        LOG(LS_ERROR) << "Parse failed. Invalid Error message?";
+        result = kInvalidMessage;
+      } else if (ValidateError(error) == kError) {
+        result = kError;
+      }  // else ignore this error message.
+      break;
+    }
+    default: {
+      ASSERT(!"Unknown message type.");
+      LOG(LS_ERROR) << "Received unknown message.";
+      result = kInvalidMessage;
+      break;
+    }
+  }
+  return result;
+}
+
+RoapSession::ParseResult RoapSession::ValidateOffer(
+    RoapOffer* received_offer) {
+
+  /* Check if the incoming OFFER has a answererSessionId, if not it is
+     an initial offer.  If the outstanding OFFER also is an initial
+     OFFER there is an Error. */
+  if (received_offer->answer_session_id().empty() &&
+      remote_id_.empty() && waiting_for_answer_) {
+    return kInvalidMessage;
+  }
+
+  if (remote_id_.empty()) {
+    remote_id_ = received_offer->offer_session_id();
+  }
+
+  // Check the message belong to this session.
+  bool result =
+      received_offer->offer_session_id() == remote_id_ &&
+      received_offer->answer_session_id() == local_id_;
+
+  if (!result || received_offer->seq() < seq_) {
+    return kInvalidMessage;  // Old seq.
+  }
+
+  if (waiting_for_answer_) {
+    if (received_offer->seq() != seq_) {
+      return kInvalidMessage;
+    }
+    // Glare.
+    if (received_offer->tie_breaker() < local_tie_breaker_) {
+      return kConflict;
+    } else if (received_offer->tie_breaker() == local_tie_breaker_) {
+      return kDoubleConflict;
+    }
+  }
+  // seq ok or remote offer won the glare resolution.
+  seq_  = received_offer->seq();
+  remote_desc_.reset(received_offer->ReleaseSessionDescription());
+  remote_candidates_ = received_offer->candidates();
+  return kOffer;
+}
+
+RoapSession::ParseResult RoapSession::ValidateAnswer(
+    RoapAnswer* received_answer) {
+  if (remote_id_.empty()) {
+    remote_id_ = received_answer->answer_session_id();
+  }
+  bool result =
+      received_answer->offer_session_id() == local_id_ &&
+      received_answer->seq() == seq_ &&
+      received_answer->answer_session_id() == remote_id_;
+  if (!result) {
+    return kInvalidMessage;
+  }
+
+  remote_desc_.reset(received_answer->ReleaseSessionDescription());
+  remote_candidates_ = received_answer->candidates();
+  if (received_answer->more_coming()) {
+    return kAnswerMoreComing;
+  }
+  waiting_for_answer_ = false;
+  return kAnswer;
+}
+
+RoapSession::ParseResult RoapSession::ValidateOk(
+    const RoapMessageBase& message) {
+  if (remote_id_.empty()) {
+    remote_id_ = message.answer_session_id();
+  }
+  bool result =
+      message.offer_session_id() == local_id_ &&
+      message.seq() == seq_ &&
+      message.answer_session_id() == remote_id_;
+  if (!result) {
+    return kInvalidMessage;
+  }
+  return kOk;
+}
+
+RoapSession::ParseResult RoapSession::ValidateError(
+    const RoapError& message) {
+  bool result =
+      message.offer_session_id() == local_id_ && message.seq() == seq_;
+
+  if (!result) {
+    return kInvalidMessage;
+  }
+  waiting_for_answer_ = false;
+  remote_error_ = message.error();
+  return kError;
+}
+
+SessionDescription* RoapSession::ReleaseRemoteDescription() {
+  return remote_desc_.release();
+}
+
+const std::vector<Candidate>& RoapSession::RemoteCandidates() {
+  return remote_candidates_;
+}
+
+RoapErrorCode RoapSession::RemoteError() {
+  return remote_error_;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/roapsession.h b/talk/app/webrtc/roapsession.h
new file mode 100644
index 0000000..7a0c27f
--- /dev/null
+++ b/talk/app/webrtc/roapsession.h
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+// This file contains a class used for creating and parsing ROAP messages
+// as defined in http://tools.ietf.org/html/draft-jennings-rtcweb-signaling-01.
+// The RoapSession is responsible for keeping track of ROAP specific
+// attributes such as offerSessionId etc of a single session but not the logic
+// for when to create a specific message.
+
+#ifndef TALK_APP_WEBRTC_ROAPSESSION_H_
+#define TALK_APP_WEBRTC_ROAPSESSION_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/roaperrorcodes.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace webrtc {
+
+using cricket::Candidate;
+using cricket::SessionDescription;
+
+class RoapAnswer;
+class RoapError;
+class RoapMessageBase;
+class RoapOffer;
+
+class RoapSession {
+ public:
+  // ParseResult is the result of parsing a message.
+  // It can be either an identified message type or a detected error.
+  enum ParseResult {
+    kOffer,
+    kAnswerMoreComing,  // More coming flag set. The SDP contains candidates.
+    kAnswer,
+    kOk,
+    kShutDown,
+    kError,
+    // The messages below is errors that can occur during parsing.
+    kConflict,  // Conflict detected during parsing of offer.
+    kDoubleConflict,  // Double conflict detected during parsing of offer.
+    kInvalidMessage  // The parsed message is invalid.
+  };
+
+  RoapSession();
+
+  // Creates a ROAP offer message based on the provided session description and
+  // candidates. This will update states in the ROAP sessions variables such as
+  // sequence number and create a local session id.
+  std::string CreateOffer(const SessionDescription* desc,
+                          const std::vector<Candidate>& candidates);
+
+  // Creates a ROAP answer message based on the provided session description and
+  // candidates. An offer must have been parsed before this function can be
+  // called.
+  std::string CreateAnswer(const SessionDescription* desc,
+                           const std::vector<Candidate>& candidates);
+  std::string CreateOk();
+  std::string CreateShutDown();
+  std::string CreateErrorMessage(RoapErrorCode error);
+  ParseResult Parse(const std::string& msg);
+  RoapErrorCode RemoteError();
+  // Get remote SessionDescription. The ownership is transferred to the caller.
+  SessionDescription* ReleaseRemoteDescription();
+  const std::vector<Candidate>& RemoteCandidates();
+
+ private:
+  ParseResult ValidateOffer(RoapOffer* received_offer);
+  ParseResult ValidateAnswer(RoapAnswer* received_answer);
+  ParseResult ValidateOk(const RoapMessageBase& message);
+  ParseResult ValidateError(const RoapError& message);
+
+  uint32 seq_;  // Sequence number of current message exchange.
+  std::string local_id_;  // offererSessionId / answerSessionId of local peer.
+  std::string remote_id_;  // offererSessionId / answerSessionId of remote peer.
+  uint32 local_tie_breaker_;  // tieBreaker of last sent offer.
+  bool waiting_for_answer_;
+
+  std::string received_offer_id_;  // offererSessionId in last received message.
+  std::string received_answer_id_;  // answerSessionId in last received message.
+  uint32 received_seq_;  // Sequence number of last received message.
+  std::string session_token_;
+  std::string response_token_;
+
+  talk_base::scoped_ptr<SessionDescription> remote_desc_;
+  std::vector<Candidate> remote_candidates_;
+
+  RoapErrorCode remote_error_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_ROAPSESSION_H_
diff --git a/talk/app/webrtc/roapsession_unittest.cc b/talk/app/webrtc/roapsession_unittest.cc
new file mode 100644
index 0000000..33cb7ab
--- /dev/null
+++ b/talk/app/webrtc/roapsession_unittest.cc
@@ -0,0 +1,350 @@
+/*
+ * 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 <string>
+
+#include "talk/app/webrtc/roapmessages.h"
+#include "talk/app/webrtc/roapsession.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/session/phone/mediasession.h"
+
+using cricket::AudioContentDescription;
+using cricket::Candidates;
+using cricket::ContentInfo;
+using cricket::SessionDescription;
+using cricket::VideoContentDescription;
+using webrtc::RoapMessageBase;
+using webrtc::RoapSession;
+using webrtc::RoapOffer;
+
+// MediaStream 1
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStream1Cname[] = "stream_1_cname";
+static const char kAudioTrackLabel1[] = "local_audio_1";
+static const uint32 kAudioTrack1Ssrc = 1;
+static const char kVideoTrackLabel1[] = "local_video_1";
+static const uint32 kVideoTrack1Ssrc = 2;
+static const char kVideoTrackLabel2[] = "local_video_2";
+static const uint32 kVideoTrack2Ssrc = 3;
+
+// MediaStream 2
+static const char kStreamLabel2[] = "local_stream_2";
+static const char kStream2Cname[] = "stream_2_cname";
+static const char kAudioTrackLabel2[] = "local_audio_2";
+static const uint32 kAudioTrack2Ssrc = 4;
+static const char kVideoTrackLabel3[] = "local_video_3";
+static const uint32 kVideoTrack3Ssrc = 5;
+
+class RoapSessionTest: public testing::Test {
+ public:
+  void SetUp() {
+    talk_base::scoped_ptr<AudioContentDescription> audio(
+        new AudioContentDescription());
+    audio->set_rtcp_mux(true);
+    cricket::StreamParams audio_stream1;
+    audio_stream1.name = kAudioTrackLabel1;
+    audio_stream1.cname = kStream1Cname;
+    audio_stream1.sync_label = kStreamLabel1;
+    audio_stream1.ssrcs.push_back(kAudioTrack1Ssrc);
+    audio->AddStream(audio_stream1);
+    desc1_.AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+                      audio.release());
+
+    talk_base::scoped_ptr<VideoContentDescription> video(
+        new VideoContentDescription());
+
+    cricket::StreamParams video_stream1;
+    video_stream1.name = kVideoTrackLabel1;
+    video_stream1.cname = kStream1Cname;
+    video_stream1.sync_label = kStreamLabel1;
+    video_stream1.ssrcs.push_back(kVideoTrack1Ssrc);
+    video->AddStream(video_stream1);
+
+    cricket::StreamParams video_stream2;
+    video_stream2.name = kVideoTrackLabel2;
+    video_stream2.cname = kStream1Cname;
+    video_stream2.sync_label = kStreamLabel1;
+    video_stream2.ssrcs.push_back(kVideoTrack2Ssrc);
+    video->AddStream(video_stream2);
+    desc1_.AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+                      video.release());
+
+    audio.reset(new AudioContentDescription());
+    audio->set_rtcp_mux(true);
+    cricket::StreamParams audio_stream2;
+    audio_stream2.name = kAudioTrackLabel2;
+    audio_stream2.cname = kStream2Cname;
+    audio_stream2.sync_label = kStreamLabel2;
+    audio_stream2.ssrcs.push_back(kAudioTrack2Ssrc);
+    audio->AddStream(audio_stream2);
+    desc2_.AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+                      audio.release());
+
+    video.reset(new VideoContentDescription());
+    cricket::StreamParams video_stream3;
+    video_stream3.name = kVideoTrackLabel3;
+    video_stream3.cname = kStream2Cname;
+    video_stream3.sync_label = kStreamLabel2;
+    video_stream3.ssrcs.push_back(kVideoTrack3Ssrc);
+    video->AddStream(video_stream3);
+    desc2_.AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+                      video.release());
+
+    int port = 1234;
+    talk_base::SocketAddress address("127.0.0.1", port++);
+    cricket::Candidate candidate1("video_rtcp", "udp", address, 1,
+        "user_video_rtcp", "password_video_rtcp", "local", "eth0", 0);
+    address.SetPort(port++);
+    cricket::Candidate candidate2("video_rtp", "udp", address, 1,
+        "user_video_rtp", "password_video_rtp", "local", "eth0", 0);
+    address.SetPort(port++);
+    cricket::Candidate candidate3("rtp", "udp", address, 1,
+        "user_rtp", "password_rtp", "local", "eth0", 0);
+    address.SetPort(port++);
+    cricket::Candidate candidate4("rtcp", "udp", address, 1,
+        "user_rtcp", "password_rtcp", "local", "eth0", 0);
+
+    candidates_.push_back(candidate1);
+    candidates_.push_back(candidate2);
+    candidates_.push_back(candidate3);
+    candidates_.push_back(candidate4);
+  }
+
+  bool CompareSessionDescription(const SessionDescription* desc1,
+                                 const SessionDescription* desc2) {
+    const ContentInfo* audio_1 = desc1->GetContentByName("audio");
+    const AudioContentDescription* audio_desc_1 =
+        static_cast<const AudioContentDescription*>(audio_1->description);
+    const ContentInfo* video_1 = desc1->GetContentByName("video");
+    const VideoContentDescription* video_desc_1 =
+        static_cast<const VideoContentDescription*>(video_1->description);
+
+    const ContentInfo* audio_2 = desc2->GetContentByName("audio");
+    const AudioContentDescription* audio_desc_2 =
+        static_cast<const AudioContentDescription*>(audio_2->description);
+    const ContentInfo* video_2 = desc2->GetContentByName("video");
+    const VideoContentDescription* video_desc_2 =
+        static_cast<const VideoContentDescription*>(video_2->description);
+
+    // Check that all streams are equal. We only check that the number of
+    // codecs are the same and leave it for other unit tests to test
+    // parsing / serialization of the session description.
+    return audio_desc_1->codecs().size() == audio_desc_2->codecs().size() &&
+        audio_desc_1->streams() == audio_desc_2->streams() &&
+        video_desc_1->codecs().size() == video_desc_2->codecs().size() &&
+        video_desc_1->streams() == video_desc_2->streams();
+  }
+
+  bool CompareCandidates(const Candidates& c1, const Candidates& c2) {
+    if (c1.size() != c2.size())
+      return false;
+
+    Candidates::const_iterator it1 = c1.begin();
+    for (; it1 != c1.end(); ++it1) {
+      // It is ok if the order in the vector have changed.
+      Candidates::const_iterator it2 = c2.begin();
+      for (; it2 != c2.end(); ++it2) {
+        if (it1->IsEquivalent(*it2)) {
+          break;
+        }
+      }
+      if (it2 == c2.end())
+        return false;
+    }
+    return true;
+  }
+
+ protected:
+  cricket::SessionDescription desc1_;
+  cricket::SessionDescription desc2_;
+  cricket::Candidates candidates_;
+};
+
+TEST_F(RoapSessionTest, OfferAnswer) {
+  RoapSession roap_session1;
+  RoapSession roap_session2;
+
+  std::string offer_message = roap_session1.CreateOffer(&desc1_, candidates_);
+
+  // Check that it is valid to send to another peer.
+  EXPECT_EQ(RoapSession::kOffer, roap_session2.Parse(offer_message));
+  talk_base::scoped_ptr<const cricket::SessionDescription> received_offer(
+      roap_session2.ReleaseRemoteDescription());
+
+  ASSERT_TRUE(received_offer.get() != NULL);
+  EXPECT_TRUE(CompareSessionDescription(&desc1_, received_offer.get()));
+  EXPECT_TRUE(CompareCandidates(candidates_, roap_session2.RemoteCandidates()));
+
+  std::string answer_message = roap_session2.CreateAnswer(&desc2_, candidates_);
+
+  EXPECT_EQ(RoapSession::kAnswer, roap_session1.Parse(answer_message));
+  talk_base::scoped_ptr<const cricket::SessionDescription> received_answer(
+      roap_session1.ReleaseRemoteDescription());
+
+  EXPECT_TRUE(CompareSessionDescription(&desc2_, received_answer.get()));
+  EXPECT_FALSE(CompareSessionDescription(received_offer.get(),
+                                         received_answer.get()));
+  EXPECT_TRUE(CompareCandidates(candidates_, roap_session1.RemoteCandidates()));
+}
+
+TEST_F(RoapSessionTest, InvalidInitialization) {
+  RoapSession roap_session1;
+  RoapSession roap_session2;
+
+  std::string offer_message1 = roap_session1.CreateOffer(&desc1_, candidates_);
+  std::string offer_message2 = roap_session2.CreateOffer(&desc2_, candidates_);
+
+  // It is an error to receive an initial offer if you have sent an
+  // initial offer.
+  EXPECT_EQ(RoapSession::kInvalidMessage,
+            roap_session1.Parse(offer_message2));
+
+  EXPECT_EQ(RoapSession::kInvalidMessage,
+            roap_session2.Parse(offer_message1));
+}
+
+TEST_F(RoapSessionTest, Glare) {
+  RoapSession roap_session1;
+  RoapSession roap_session2;
+
+  // Setup. Need to exchange an offer and an answer in order to test for glare.
+  std::string offer_message1 = roap_session1.CreateOffer(&desc1_, candidates_);
+
+  roap_session2.Parse(offer_message1);
+  talk_base::scoped_ptr<const SessionDescription> received_offer(
+      roap_session2.ReleaseRemoteDescription());
+  std::string answer_message2 = roap_session2.CreateAnswer(&desc2_,
+                                                           candidates_);
+  roap_session1.Parse(answer_message2);
+
+  // Ok- we should now have all we need. Create a glare condition by
+  // updating the offer simultaneously.
+  offer_message1 = roap_session1.CreateOffer(&desc2_, candidates_);
+  std::string offer_message2 = roap_session2.CreateOffer(&desc1_, candidates_);
+
+  EXPECT_TRUE(
+      (RoapSession::kOffer == roap_session1.Parse(offer_message2) &&
+      RoapSession::kConflict == roap_session2.Parse(offer_message1)) ||
+      (RoapSession::kOffer == roap_session2.Parse(offer_message1) &&
+      RoapSession::kConflict == roap_session1.Parse(offer_message2)));
+}
+
+// Test Glare resolution by setting different TieBreakers.
+TEST_F(RoapSessionTest, TieBreaker) {
+  RoapSession roap_session1;
+  RoapSession roap_session2;
+
+  // Offer 1
+  std::string offer_message1 = roap_session1.CreateOffer(&desc1_, candidates_);
+
+  EXPECT_EQ(RoapSession::kOffer, roap_session2.Parse(offer_message1));
+  talk_base::scoped_ptr<const SessionDescription> received_offer(
+      roap_session2.ReleaseRemoteDescription());
+  std::string answer_message2 = roap_session2.CreateAnswer(&desc2_,
+                                                           candidates_);
+
+  EXPECT_EQ(RoapSession::kAnswer, roap_session1.Parse(answer_message2));
+
+  // Ok- we should now have all we need. Create a double conflict condition.
+  offer_message1 = roap_session1.CreateOffer(&desc2_, candidates_);
+  RoapMessageBase message_base;
+  EXPECT_TRUE(message_base.Parse(offer_message1));
+  RoapOffer message_offer(message_base);
+  EXPECT_TRUE(message_offer.Parse());
+  RoapOffer double_conflict_offer(message_offer.answer_session_id(),
+                                  message_offer.offer_session_id(),
+                                  "",
+                                  message_offer.seq(),
+                                  message_offer.tie_breaker(),
+                                  &desc1_,
+                                  candidates_);
+  EXPECT_EQ(RoapSession::kDoubleConflict,
+            roap_session1.Parse(double_conflict_offer.Serialize()));
+
+  RoapOffer losing_offer(message_offer.answer_session_id(),
+                         message_offer.offer_session_id(),
+                         "",
+                         message_offer.seq(),
+                         0,
+                         &desc1_,
+                         candidates_);
+  EXPECT_EQ(RoapSession::kConflict,
+            roap_session1.Parse(losing_offer.Serialize()));
+
+  RoapOffer winning_offer(message_offer.answer_session_id(),
+                          message_offer.offer_session_id(),
+                          "",
+                          message_offer.seq(),
+                          0xFFFFFFFF,
+                          &desc1_,
+                          candidates_);
+  EXPECT_EQ(RoapSession::kOffer,
+            roap_session1.Parse(winning_offer.Serialize()));
+}
+
+TEST_F(RoapSessionTest, ShutDownOk) {
+  RoapSession roap_session1;
+  std::string shutdown = roap_session1.CreateShutDown();
+
+  RoapSession roap_session2;
+  EXPECT_EQ(RoapSession::kShutDown, roap_session2.Parse(shutdown));
+
+  std::string ok_message = roap_session2.CreateOk();
+  EXPECT_EQ(RoapSession::kOk, roap_session1.Parse(ok_message));
+}
+
+TEST_F(RoapSessionTest, ErrorMessageCreation) {
+  RoapSession roap_session1;
+  RoapSession roap_session2;
+
+  std::string message = roap_session1.CreateErrorMessage(webrtc::kNoMatch);
+  EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+  EXPECT_EQ(webrtc::kNoMatch, roap_session2.RemoteError());
+
+  message = roap_session1.CreateErrorMessage(webrtc::kTimeout);
+  EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+  EXPECT_EQ(webrtc::kTimeout, roap_session2.RemoteError());
+
+  message = roap_session1.CreateErrorMessage(webrtc::kRefused);
+  EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+  EXPECT_EQ(webrtc::kRefused, roap_session2.RemoteError());
+
+  message = roap_session1.CreateErrorMessage(webrtc::kConflict);
+  EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+  EXPECT_EQ(webrtc::kConflict, roap_session2.RemoteError());
+
+  message = roap_session1.CreateErrorMessage(webrtc::kDoubleConflict);
+  EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+  EXPECT_EQ(webrtc::kDoubleConflict, roap_session2.RemoteError());
+
+  message = roap_session1.CreateErrorMessage(webrtc::kFailed);
+  EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+  EXPECT_EQ(webrtc::kFailed, roap_session2.RemoteError());
+}
diff --git a/talk/app/webrtc/sessiondescriptionprovider.h b/talk/app/webrtc/sessiondescriptionprovider.h
new file mode 100644
index 0000000..7142cdd
--- /dev/null
+++ b/talk/app/webrtc/sessiondescriptionprovider.h
@@ -0,0 +1,72 @@
+/*
+ * 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_APP_WEBRTC_SESSIONDESCRIPTIONPROVIDER_H_
+#define TALK_APP_WEBRTC_SESSIONDESCRIPTIONPROVIDER_H_
+
+#include "talk/session/phone/mediasession.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace webrtc {
+
+class SessionDescriptionProvider {
+ public:
+  // Creates a new SessionDescription based on |options|.
+  // It does not affect the currently set local or remote SessionDescription.
+  // Caller is owner of the created SessionDescription.
+  virtual cricket::SessionDescription* CreateOffer(
+      const cricket::MediaSessionOptions& options) = 0;
+
+  // Creates a new SessionDescription based on |offer| and |options|.
+  // It does not affect the currently set local or remote SessionDescription.
+  // Caller is owner of the created SessionDescription.
+  virtual cricket::SessionDescription* CreateAnswer(
+      const cricket::SessionDescription* offer,
+      const cricket::MediaSessionOptions& options) = 0;
+
+  // Transfers ownership of session description.
+  virtual void SetLocalDescription(const cricket::SessionDescription* desc,
+                                   cricket::ContentAction type) = 0;
+  virtual void SetRemoteDescription(cricket::SessionDescription* desc,
+                                    cricket::ContentAction type) = 0;
+  // Sets all the currently known remote candidates.
+  virtual void SetRemoteCandidates(
+      const std::vector<cricket::Candidate>& remote_candidates) = 0;
+
+  // Current local session description.
+  virtual const cricket::SessionDescription* local_description() const = 0;
+  // Current remote session description.
+  virtual const cricket::SessionDescription* remote_description() const = 0;
+
+ protected:
+  virtual ~SessionDescriptionProvider() {}
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_SESSIONDESCRIPTIONPROVIDER_H_
diff --git a/talk/app/webrtc/streamcollectionimpl.h b/talk/app/webrtc/streamcollectionimpl.h
new file mode 100644
index 0000000..76df3fd
--- /dev/null
+++ b/talk/app/webrtc/streamcollectionimpl.h
@@ -0,0 +1,103 @@
+/*
+ * 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_APP_WEBRTC_STREAMCOLLECTIONIMPL_H_
+#define TALK_APP_WEBRTC_STREAMCOLLECTIONIMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/peerconnection.h"
+
+namespace webrtc {
+
+// Implementation of StreamCollection.
+class StreamCollection : public StreamCollectionInterface {
+ public:
+  static talk_base::scoped_refptr<StreamCollection> Create() {
+    talk_base::RefCountedObject<StreamCollection>* implementation =
+         new talk_base::RefCountedObject<StreamCollection>();
+    return implementation;
+  }
+
+  static talk_base::scoped_refptr<StreamCollection> Create(
+      StreamCollection* streams) {
+    talk_base::RefCountedObject<StreamCollection>* implementation =
+         new talk_base::RefCountedObject<StreamCollection>(streams);
+    return implementation;
+  }
+
+  virtual size_t count() {
+    return media_streams_.size();
+  }
+
+  virtual MediaStreamInterface* at(size_t index) {
+    return media_streams_.at(index);
+  }
+
+  virtual MediaStreamInterface* find(const std::string& label) {
+    for (StreamVector::iterator it = media_streams_.begin();
+         it != media_streams_.end(); ++it) {
+      if ((*it)->label().compare(label) == 0) {
+        return (*it);
+      }
+    }
+    return NULL;
+  }
+
+  void AddStream(MediaStreamInterface* stream) {
+    for (StreamVector::iterator it = media_streams_.begin();
+         it != media_streams_.end(); ++it) {
+      if ((*it)->label().compare(stream->label()) == 0)
+        return;
+    }
+    media_streams_.push_back(stream);
+  }
+
+  void RemoveStream(MediaStreamInterface* remove_stream) {
+    for (StreamVector::iterator it = media_streams_.begin();
+         it != media_streams_.end(); ++it) {
+      if ((*it)->label().compare(remove_stream->label()) == 0) {
+        media_streams_.erase(it);
+        break;
+      }
+    }
+  }
+
+ protected:
+  StreamCollection() {}
+  explicit StreamCollection(StreamCollection* original)
+      : media_streams_(original->media_streams_) {
+  }
+  typedef std::vector<talk_base::scoped_refptr<MediaStreamInterface> >
+      StreamVector;
+  StreamVector media_streams_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_STREAMCOLLECTIONIMPL_H_
diff --git a/talk/app/webrtc/videorendererimpl.cc b/talk/app/webrtc/videorendererimpl.cc
new file mode 100644
index 0000000..6355e4b
--- /dev/null
+++ b/talk/app/webrtc/videorendererimpl.cc
@@ -0,0 +1,58 @@
+/*
+ * 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/mediastreamimpl.h"
+
+#include "talk/session/phone/videorenderer.h"
+
+namespace webrtc {
+
+// VideoRendererImpl take ownership of cricket::VideoRenderer.
+class VideoRendererImpl : public VideoRendererWrapperInterface {
+ public:
+  explicit VideoRendererImpl(cricket::VideoRenderer* renderer)
+      : renderer_(renderer) {
+  }
+  virtual cricket::VideoRenderer* renderer() {
+    return renderer_;
+  }
+ protected:
+  ~VideoRendererImpl() {
+    delete renderer_;
+  }
+ private:
+  cricket::VideoRenderer* renderer_;
+};
+
+talk_base::scoped_refptr<VideoRendererWrapperInterface> CreateVideoRenderer(
+    cricket::VideoRenderer* renderer) {
+  talk_base::RefCountedObject<VideoRendererImpl>* r =
+      new talk_base::RefCountedObject<VideoRendererImpl>(renderer);
+  return r;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/videotrackimpl.cc b/talk/app/webrtc/videotrackimpl.cc
new file mode 100644
index 0000000..0fb218a
--- /dev/null
+++ b/talk/app/webrtc/videotrackimpl.cc
@@ -0,0 +1,82 @@
+/*
+ * 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/app/webrtc/videotrackimpl.h"
+
+#include <string>
+
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+namespace webrtc {
+
+static const char kVideoTrackKind[] = "video";
+
+VideoTrack::VideoTrack(const std::string& label)
+    : MediaStreamTrack<LocalVideoTrackInterface>(label),
+      video_device_(NULL) {
+}
+
+VideoTrack::VideoTrack(const std::string& label,
+                       cricket::VideoCapturer* video_device)
+    : MediaStreamTrack<LocalVideoTrackInterface>(label),
+      video_device_(NULL) {
+  video_device_.reset(video_device);
+}
+
+void VideoTrack::SetRenderer(VideoRendererWrapperInterface* renderer) {
+  video_renderer_ = renderer;
+  Notifier<LocalVideoTrackInterface>::FireOnChanged();
+}
+
+VideoRendererWrapperInterface* VideoTrack::GetRenderer() {
+  return video_renderer_.get();
+}
+
+  // Get the VideoCapture device associated with this track.
+cricket::VideoCapturer* VideoTrack::GetVideoCapture() {
+  return video_device_.get();
+}
+
+std::string VideoTrack::kind() const {
+  return kVideoTrackKind;
+}
+
+talk_base::scoped_refptr<VideoTrack> VideoTrack::CreateRemote(
+    const std::string& label) {
+  talk_base::RefCountedObject<VideoTrack>* track =
+      new talk_base::RefCountedObject<VideoTrack>(label);
+  return track;
+}
+
+talk_base::scoped_refptr<VideoTrack> VideoTrack::CreateLocal(
+    const std::string& label,
+    cricket::VideoCapturer* video_device) {
+  talk_base::RefCountedObject<VideoTrack>* track =
+      new talk_base::RefCountedObject<VideoTrack>(label, video_device);
+  return track;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/videotrackimpl.h b/talk/app/webrtc/videotrackimpl.h
new file mode 100644
index 0000000..de99e6a
--- /dev/null
+++ b/talk/app/webrtc/videotrackimpl.h
@@ -0,0 +1,80 @@
+/*
+ * 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_APP_WEBRTC_VIDEOTRACKIMPL_H_
+#define TALK_APP_WEBRTC_VIDEOTRACKIMPL_H_
+
+#include <string>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/mediatrackimpl.h"
+#include "talk/app/webrtc/notifierimpl.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_capture/main/interface/video_capture.h"
+#else
+#include "third_party/webrtc/files/include/video_capture.h"
+#endif
+
+namespace cricket {
+
+class VideoCapturer;
+
+}  // namespace cricket
+
+namespace webrtc {
+
+class VideoTrack : public MediaStreamTrack<LocalVideoTrackInterface> {
+ public:
+  // Create a video track used for remote video tracks.
+  static talk_base::scoped_refptr<VideoTrack> CreateRemote(
+      const std::string& label);
+  // Create a video track used for local video tracks.
+  static talk_base::scoped_refptr<VideoTrack> CreateLocal(
+      const std::string& label,
+      cricket::VideoCapturer* video_device);
+
+  virtual cricket::VideoCapturer* GetVideoCapture();
+  virtual void SetRenderer(VideoRendererWrapperInterface* renderer);
+  VideoRendererWrapperInterface* GetRenderer();
+
+  virtual std::string kind() const;
+
+ protected:
+  explicit VideoTrack(const std::string& label);
+  VideoTrack(const std::string& label, cricket::VideoCapturer* video_device);
+
+ private:
+  talk_base::scoped_ptr<cricket::VideoCapturer> video_device_;
+  talk_base::scoped_refptr<VideoRendererWrapperInterface> video_renderer_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_VIDEOTRACKIMPL_H_
diff --git a/talk/app/webrtc/webrtc.scons b/talk/app/webrtc/webrtc.scons
index 271d413..88ea3f4 100644
--- a/talk/app/webrtc/webrtc.scons
+++ b/talk/app/webrtc/webrtc.scons
@@ -3,58 +3,68 @@
 
 Import('env')
 
-# local sources
-talk.Library(
-  env,
-  name = 'webrtc',
-  srcs = [
-    'peerconnectionimpl.cc',
-    'peerconnectionproxy.cc',
-    'peerconnectionfactory.cc',
-    'webrtcjson.cc',
-    'webrtcsession.cc',
-  ],
-)
+if env.Bit('have_webrtc_voice') and env.Bit('have_webrtc_video'):
+  # local sources
+  talk.Library(
+    env,
+    name = 'peerconnection',
+    srcs = [
+      'audiotrackimpl.cc',
+      'mediastreamhandler.cc',
+      'mediastreamimpl.cc',
+      'mediastreamproxy.cc',
+      'mediastreamtrackproxy.cc',
+      'peerconnectionfactoryimpl.cc',
+      'peerconnectionimpl.cc',
+      'peerconnectionsignaling.cc',
+      'portallocatorfactory.cc',
+      'roapmessages.cc',
+      'roapsession.cc',
+      'videorendererimpl.cc',
+      'videotrackimpl.cc',
+      'webrtcsdp.cc',
+      'webrtcsession.cc',
+    ],
+  )
 
-talk.Unittest(
-  env,
-  name = 'webrtc',
-  srcs = [
-    'peerconnection_unittest.cc',
-    'unittest_utilities.cc',
-    'webrtcsession_unittest.cc',
-  ],
-  libs = [
-    'base',
-    'expat',
-    'jpeg',
-    'json',
-    'webrtc',
-    'p2p',
-    'phone',
-    'srtp',
-    'xmpp',
-    'xmllite',
-    'yuvscaler'
-  ],
-  include_talk_media_libs = True,
-  mac_libs = [
-    'crypto',
-    'ssl',
-  ],
-  mac_FRAMEWORKS = [
-    'Foundation',
-    'IOKit',
-    'QTKit',
-  ],
-  win_link_flags = [('', '/nodefaultlib:libcmt')[env.Bit('debug')]],
-  lin_libs = [
-    'rt',
-    'dl',
-    'sound',
-    'X11',
-    'Xext',
-    'Xfixes',
-    'Xrandr'
-  ],
-)
+  talk.Unittest(
+    env,
+    name = 'peerconnection',
+    srcs = [
+      'test/fakevideocapturemodule.cc',
+      'test/fileframesource.cc',
+      'test/i420framesource.cc',
+      'test/staticframesource.cc',
+      'mediastream_unittest.cc',
+      'mediastreamhandler_unittest.cc',
+      'peerconnectionimpl_unittest.cc',
+      'peerconnection_unittest.cc',
+      'peerconnectionfactory_unittest.cc',
+      'peerconnectionsignaling_unittest.cc',
+      'roapmessages_unittest.cc',
+      'roapsession_unittest.cc',
+      'webrtcsdp_unittest.cc',
+      'webrtcsession_unittest.cc',
+    ],
+    libs = [
+      'base',
+      'expat',
+      'jpeg',
+      'json',
+      'p2p',
+      'phone',
+      'srtp',
+      'xmllite',
+      'xmpp',
+      'yuvscaler',
+      'peerconnection',
+    ],
+    win_link_flags = [('', '/nodefaultlib:libcmt')[env.Bit('debug')]],
+    lin_libs = [
+      'sound',
+    ],
+    mac_libs = [
+      'crypto',
+      'ssl',
+    ],
+   )
diff --git a/talk/app/webrtc/webrtcjson.cc b/talk/app/webrtc/webrtcjson.cc
index 1662e23..2aaac97 100644
--- a/talk/app/webrtc/webrtcjson.cc
+++ b/talk/app/webrtc/webrtcjson.cc
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * 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:
@@ -29,94 +29,161 @@
 
 #include <stdio.h>
 #include <string>
+#include <vector>
 
 #include "talk/base/json.h"
 #include "talk/base/logging.h"
 #include "talk/base/stringutils.h"
 #include "talk/session/phone/codec.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/session/phone/mediasession.h"
 #include "talk/session/phone/mediasessionclient.h"
 
 namespace webrtc {
 static const int kIceComponent = 1;
 static const int kIceFoundation = 1;
 
-bool GetJsonSignalingMessage(
+static std::vector<Json::Value> ReadValues(const Json::Value& value,
+                                           const std::string& key);
+
+static void BuildContent(
     const cricket::SessionDescription* sdp,
-    const std::vector<cricket::Candidate>& candidates,
-    std::string* signaling_message) {
-  const cricket::ContentInfo* audio_content = GetFirstAudioContent(sdp);
-  const cricket::ContentInfo* video_content = GetFirstVideoContent(sdp);
-
-  std::vector<Json::Value> media;
-  if (audio_content) {
-    Json::Value value;
-    BuildMediaMessage(*audio_content, candidates, false, &value);
-    media.push_back(value);
-  }
-
-  if (video_content) {
-    Json::Value value;
-    BuildMediaMessage(*video_content, candidates, true, &value);
-    media.push_back(value);
-  }
-
-  Json::Value signal;
-  Append(&signal, "media", media);
-
-  // Now serialize.
-  *signaling_message = Serialize(signal);
-
-  return true;
-}
-
-bool BuildMediaMessage(
     const cricket::ContentInfo& content_info,
     const std::vector<cricket::Candidate>& candidates,
     bool video,
-    Json::Value* params) {
+    Json::Value* content);
+
+static void BuildCandidate(const std::vector<cricket::Candidate>& candidates,
+                           bool video,
+                           std::vector<Json::Value>* jcandidates);
+
+static void BuildRtpMapParams(const cricket::ContentInfo& content_info,
+                              bool video,
+                              std::vector<Json::Value>* rtpmap);
+
+static void BuildCrypto(const cricket::ContentInfo& content_info,
+                        bool video,
+                        std::vector<Json::Value>* cryptos);
+
+static void BuildTrack(const cricket::SessionDescription* sdp,
+                       bool video,
+                       std::vector<Json::Value>* track);
+
+bool ParseContent(const Json::Value& jmessage,
+    cricket::SessionDescription* sdp,
+    std::vector<cricket::Candidate>* candidates);
+
+static bool ParseAudioCodec(const Json::Value& value,
+                            cricket::AudioContentDescription* content);
+static bool ParseVideoCodec(const Json::Value& value,
+                            cricket::VideoContentDescription* content);
+static bool ParseCrypto(const Json::Value& content,
+                        cricket::MediaContentDescription* desc);
+
+static bool ParseCandidates(const Json::Value& content,
+                            std::vector<cricket::Candidate>* candidates);
+
+static bool ParseTrack(const Json::Value& content,
+    cricket::MediaContentDescription* content_desc);
+
+static void Append(Json::Value* object, const std::string& key, bool value);
+static void Append(Json::Value* object, const std::string& key,
+                   const char* value);
+static void Append(Json::Value* object, const std::string& key, int value);
+static void Append(Json::Value* object, const std::string& key,
+                   const std::string& value);
+static void Append(Json::Value* object, const std::string& key, uint32 value);
+static void Append(Json::Value* object, const std::string& key,
+                   const Json::Value& value);
+static void Append(Json::Value* object,
+                   const std::string& key,
+                   const std::vector<Json::Value>& values);
+
+void JsonSerializeSessionDescription(
+    const cricket::SessionDescription* sdp,
+    const std::vector<cricket::Candidate>& candidates,
+    Json::Value* message) {
+
+  const cricket::ContentInfo* audio_content = GetFirstAudioContent(sdp);
+  const cricket::ContentInfo* video_content = GetFirstVideoContent(sdp);
+
+  std::vector<Json::Value> together;
+  together.push_back("audio");
+  together.push_back("video");
+
+  std::vector<Json::Value> contents;
+
+  if (audio_content) {
+    Json::Value content;
+    BuildContent(sdp, *audio_content, candidates, false, &content);
+    contents.push_back(content);
+  }
+
+  if (video_content) {
+    Json::Value content;
+    BuildContent(sdp, *video_content, candidates, true, &content);
+    contents.push_back(content);
+  }
+
+  Append(message, "content", contents);
+  Append(message, "TOGETHER", together);
+}
+
+void BuildContent(
+    const cricket::SessionDescription* sdp,
+    const cricket::ContentInfo& content_info,
+    const std::vector<cricket::Candidate>& candidates,
+    bool video,
+    Json::Value* content) {
+  std::string label("media");
+  // TODO: Use enum instead of bool video to prepare for other
+  // media types such as the data media stream.
   if (video) {
-    Append(params, "label", 2);  // always video 2
+    Append(content, label, "video");
   } else {
-    Append(params, "label", 1);  // always audio 1
+    Append(content, label, "audio");
   }
 
   const cricket::MediaContentDescription* media_info =
-  static_cast<const cricket::MediaContentDescription*> (
-      content_info.description);
+      static_cast<const cricket::MediaContentDescription*> (
+          content_info.description);
   if (media_info->rtcp_mux()) {
-    Append(params, "rtcp_mux", true);
+    Append(content, "rtcp_mux", true);
   }
 
+  // rtpmap
   std::vector<Json::Value> rtpmap;
-  if (!BuildRtpMapParams(content_info, video, &rtpmap)) {
-    return false;
-  }
+  BuildRtpMapParams(content_info, video, &rtpmap);
+  Append(content, "rtpmap", rtpmap);
 
-  Append(params, "rtpmap", rtpmap);
+  // crypto
+  std::vector<Json::Value> crypto;
+  BuildCrypto(content_info, video, &crypto);
+  Append(content, "crypto", crypto);
 
-  Json::Value attributes;
+  // candidate
   std::vector<Json::Value> jcandidates;
+  BuildCandidate(candidates, video, &jcandidates);
+  Append(content, "candidate", jcandidates);
 
-  if (!BuildAttributes(candidates, video, &jcandidates)) {
-    return false;
-  }
-  Append(&attributes, "candidate", jcandidates);
-  Append(params, "attributes", attributes);
-  return true;
+  // track
+  std::vector<Json::Value> track;
+  BuildTrack(sdp, video, &track);
+  Append(content, "track", track);
 }
 
-bool BuildRtpMapParams(const cricket::ContentInfo& content_info,
+void BuildRtpMapParams(const cricket::ContentInfo& content_info,
                        bool video,
                        std::vector<Json::Value>* rtpmap) {
   if (!video) {
-    const cricket::AudioContentDescription* audio_offer =
+    const cricket::AudioContentDescription* audio =
         static_cast<const cricket::AudioContentDescription*>(
             content_info.description);
 
     std::vector<cricket::AudioCodec>::const_iterator iter =
-        audio_offer->codecs().begin();
+        audio->codecs().begin();
     std::vector<cricket::AudioCodec>::const_iterator iter_end =
-        audio_offer->codecs().end();
+        audio->codecs().end();
     for (; iter != iter_end; ++iter) {
       Json::Value codec;
       std::string codec_str(std::string("audio/").append(iter->name));
@@ -128,14 +195,14 @@
       rtpmap->push_back(codec_id);
     }
   } else {
-    const cricket::VideoContentDescription* video_offer =
+    const cricket::VideoContentDescription* video =
         static_cast<const cricket::VideoContentDescription*>(
             content_info.description);
 
     std::vector<cricket::VideoCodec>::const_iterator iter =
-        video_offer->codecs().begin();
+        video->codecs().begin();
     std::vector<cricket::VideoCodec>::const_iterator iter_end =
-        video_offer->codecs().end();
+        video->codecs().end();
     for (; iter != iter_end; ++iter) {
       Json::Value codec;
       std::string codec_str(std::string("video/").append(iter->name));
@@ -145,12 +212,29 @@
       rtpmap->push_back(codec_id);
     }
   }
-  return true;
 }
 
-bool BuildAttributes(const std::vector<cricket::Candidate>& candidates,
-                     bool video,
-                     std::vector<Json::Value>* jcandidates) {
+void BuildCrypto(const cricket::ContentInfo& content_info,
+                 bool video,
+                 std::vector<Json::Value>* cryptos) {
+  const cricket::MediaContentDescription* content_desc =
+      static_cast<const cricket::MediaContentDescription*>(
+          content_info.description);
+  std::vector<cricket::CryptoParams>::const_iterator iter =
+      content_desc->cryptos().begin();
+  std::vector<cricket::CryptoParams>::const_iterator iter_end =
+      content_desc->cryptos().end();
+  for (; iter != iter_end; ++iter) {
+    Json::Value crypto;
+    Append(&crypto, "cipher_suite", iter->cipher_suite);
+    Append(&crypto, "key_params", iter->key_params);
+    cryptos->push_back(crypto);
+  }
+}
+
+void BuildCandidate(const std::vector<cricket::Candidate>& candidates,
+                    bool video,
+                    std::vector<Json::Value>* jcandidates) {
   std::vector<cricket::Candidate>::const_iterator iter =
       candidates.begin();
   std::vector<cricket::Candidate>::const_iterator iter_end =
@@ -160,95 +244,117 @@
                   (!iter->name().compare("video_rtp")))) ||
         (!video && (!iter->name().compare("rtp") ||
                    (!iter->name().compare("rtcp"))))) {
-      Json::Value candidate;
-      Append(&candidate, "component", kIceComponent);
-      Append(&candidate, "foundation", kIceFoundation);
-      Append(&candidate, "generation", iter->generation());
-      Append(&candidate, "proto", iter->protocol());
-      Append(&candidate, "priority", iter->preference_str());
-      Append(&candidate, "ip", iter->address().IPAsString());
-      Append(&candidate, "port", iter->address().PortAsString());
-      Append(&candidate, "type", iter->type());
-      Append(&candidate, "name", iter->name());
-      Append(&candidate, "network_name", iter->network_name());
-      Append(&candidate, "username", iter->username());
-      Append(&candidate, "password", iter->password());
-      jcandidates->push_back(candidate);
+      Json::Value jcandidate;
+      Append(&jcandidate, "component", kIceComponent);
+      Append(&jcandidate, "foundation", kIceFoundation);
+      Append(&jcandidate, "generation", iter->generation());
+      Append(&jcandidate, "proto", iter->protocol());
+      Append(&jcandidate, "priority", iter->preference_str());
+      Append(&jcandidate, "ip", iter->address().IPAsString());
+      Append(&jcandidate, "port", iter->address().PortAsString());
+      Append(&jcandidate, "type", iter->type());
+      Append(&jcandidate, "name", iter->name());
+      Append(&jcandidate, "network_name", iter->network_name());
+      Append(&jcandidate, "username", iter->username());
+      Append(&jcandidate, "password", iter->password());
+      jcandidates->push_back(jcandidate);
     }
   }
-  return true;
 }
 
-std::string Serialize(const Json::Value& value) {
-  Json::StyledWriter writer;
-  return writer.write(value);
-}
+void BuildTrack(const cricket::SessionDescription* sdp,
+                bool video,
+                std::vector<Json::Value>* tracks) {
+  const cricket::ContentInfo* content;
+  if (video)
+    content = GetFirstVideoContent(sdp);
+  else
+    content = GetFirstAudioContent(sdp);
 
-bool Deserialize(const std::string& message, Json::Value* value) {
-  Json::Reader reader;
-  return reader.parse(message, *value);
-}
+  if (!content)
+    return;
 
-bool ParseJsonSignalingMessage(const std::string& signaling_message,
-                               cricket::SessionDescription** sdp,
-                               std::vector<cricket::Candidate>* candidates) {
-  ASSERT(!(*sdp));  // expect this to be NULL
-  // first deserialize message
-  Json::Value value;
-  if (!Deserialize(signaling_message, &value)) {
-    return false;
+  const cricket::MediaContentDescription* desc =
+      static_cast<const cricket::MediaContentDescription*>(
+          content->description);
+  for (cricket::StreamParamsVec::const_iterator it = desc->streams().begin();
+       it != desc->streams().end();
+       ++it) {
+    // TODO: Support ssrcsgroups.
+    Json::Value track;
+    Append(&track, "ssrc", it->ssrcs[0]);
+    Append(&track, "cname", it->cname);
+    Append(&track, "stream_label", it->sync_label);
+    Append(&track, "label", it->name);
+    tracks->push_back(track);
   }
+}
 
-  // get media objects
-  std::vector<Json::Value> mlines = ReadValues(value, "media");
-  if (mlines.empty()) {
-    // no m-lines found
+bool JsonDeserializeSessionDescription(
+    const Json::Value& message,
+    cricket::SessionDescription* sdp,
+    std::vector<cricket::Candidate>* candidates) {
+
+  ASSERT(sdp != NULL);
+  ASSERT(candidates != NULL);
+
+  if (sdp == NULL || candidates == NULL)
     return false;
-  }
 
-  *sdp = new cricket::SessionDescription();
-
-  // get codec information
-  for (size_t i = 0; i < mlines.size(); ++i) {
-    if (mlines[i]["label"].asInt() == 1) {
+  // Get content
+  std::vector<Json::Value> contents = ReadValues(message, "content");
+  if (contents.size() == 0)
+    return false;
+  for (size_t i = 0; i < contents.size(); ++i) {
+    Json::Value content = contents[i];
+    // candidates
+    if (!ParseCandidates(content, candidates))
+      return false;
+    // rtcp_mux
+    bool rtcp_mux;
+    if (!GetBoolFromJsonObject(content, "rtcp_mux", &rtcp_mux))
+      rtcp_mux = false;
+    // rtpmap
+    if (content["media"].asString().compare("audio") == 0) {
       cricket::AudioContentDescription* audio_content =
           new cricket::AudioContentDescription();
-      ParseAudioCodec(mlines[i], audio_content);
-      audio_content->set_rtcp_mux(ParseRtcpMux(mlines[i]));
+      if (!ParseAudioCodec(content, audio_content))
+        return false;
+      audio_content->set_rtcp_mux(rtcp_mux);
       audio_content->SortCodecs();
-      (*sdp)->AddContent(cricket::CN_AUDIO,
-                         cricket::NS_JINGLE_RTP, audio_content);
-      ParseIceCandidates(mlines[i], candidates);
-    } else {
+      // crypto
+      if (!ParseCrypto(content, audio_content))
+        return false;
+      // tracks
+      if (!ParseTrack(content, audio_content))
+        return false;
+      (sdp)->AddContent(cricket::CN_AUDIO,
+                        cricket::NS_JINGLE_RTP, audio_content);
+    } else if (content["media"].asString().compare("video") == 0) {
       cricket::VideoContentDescription* video_content =
           new cricket::VideoContentDescription();
-      ParseVideoCodec(mlines[i], video_content);
-
-      video_content->set_rtcp_mux(ParseRtcpMux(mlines[i]));
+      if (!ParseVideoCodec(content, video_content))
+        return false;
+      video_content->set_rtcp_mux(rtcp_mux);
       video_content->SortCodecs();
-      (*sdp)->AddContent(cricket::CN_VIDEO,
-                         cricket::NS_JINGLE_RTP, video_content);
-      ParseIceCandidates(mlines[i], candidates);
+      // crypto
+      if (!ParseCrypto(content, video_content))
+        return false;
+      if (!ParseTrack(content, video_content))
+        return false;
+      (sdp)->AddContent(cricket::CN_VIDEO,
+                        cricket::NS_JINGLE_RTP, video_content);
     }
   }
   return true;
 }
 
-bool ParseRtcpMux(const Json::Value& value) {
-  Json::Value rtcp_mux(ReadValue(value, "rtcp_mux"));
-  if (!rtcp_mux.empty()) {
-    if (rtcp_mux.asBool()) {
-      return true;
-    }
-  }
-  return false;
-}
-
 bool ParseAudioCodec(const Json::Value& value,
                      cricket::AudioContentDescription* content) {
   std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap"));
+  // When there's no codecs in common, rtpmap can be empty.
   if (rtpmap.empty())
-    return false;
+    return true;
 
   std::vector<Json::Value>::const_iterator iter =
       rtpmap.begin();
@@ -259,11 +365,13 @@
     std::string pltype(iter->begin().memberName());
     talk_base::FromString(pltype, &codec.id);
     Json::Value codec_info((*iter)[pltype]);
-    std::string codec_name(ReadString(codec_info, "codec"));
+    std::string codec_name;
+    if (!GetStringFromJsonObject(codec_info, "codec", &codec_name))
+      continue;
     std::vector<std::string> tokens;
     talk_base::split(codec_name, '/', &tokens);
     codec.name = tokens[1];
-    codec.clockrate = ReadUInt(codec_info, "clockrate");
+    GetIntFromJsonObject(codec_info, "clockrate", &codec.clockrate);
     content->AddCodec(codec);
   }
 
@@ -273,8 +381,9 @@
 bool ParseVideoCodec(const Json::Value& value,
                      cricket::VideoContentDescription* content) {
   std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap"));
+  // When there's no codecs in common, rtpmap can be empty.
   if (rtpmap.empty())
-    return false;
+    return true;
 
   std::vector<Json::Value>::const_iterator iter =
       rtpmap.begin();
@@ -293,14 +402,9 @@
   return true;
 }
 
-bool ParseIceCandidates(const Json::Value& value,
-                        std::vector<cricket::Candidate>* candidates) {
-  Json::Value attributes(ReadValue(value, "attributes"));
-  std::string ice_pwd(ReadString(attributes, "ice-pwd"));
-  std::string ice_ufrag(ReadString(attributes, "ice-ufrag"));
-
-  std::vector<Json::Value> jcandidates(ReadValues(attributes, "candidate"));
-
+bool ParseCandidates(const Json::Value& content,
+                     std::vector<cricket::Candidate>* candidates) {
+  std::vector<Json::Value> jcandidates(ReadValues(content, "candidate"));
   std::vector<Json::Value>::const_iterator iter =
       jcandidates.begin();
   std::vector<Json::Value>::const_iterator iter_end =
@@ -361,6 +465,68 @@
   return true;
 }
 
+bool ParseCrypto(const Json::Value& content,
+                 cricket::MediaContentDescription* desc) {
+  std::vector<Json::Value> jcryptos(ReadValues(content, "crypto"));
+  std::vector<Json::Value>::const_iterator iter =
+      jcryptos.begin();
+  std::vector<Json::Value>::const_iterator iter_end =
+      jcryptos.end();
+  for (; iter != iter_end; ++iter) {
+    cricket::CryptoParams crypto;
+
+    std::string cipher_suite;
+    if (!GetStringFromJsonObject(*iter, "cipher_suite", &cipher_suite))
+      return false;
+    crypto.cipher_suite = cipher_suite;
+
+    std::string key_params;
+    if (!GetStringFromJsonObject(*iter, "key_params", &key_params))
+      return false;
+    crypto.key_params= key_params;
+
+    desc->AddCrypto(crypto);
+  }
+  return true;
+}
+
+bool ParseTrack(const Json::Value& content,
+    cricket::MediaContentDescription* content_desc) {
+  ASSERT(content_desc != NULL);
+  if (!content_desc)
+    return false;
+
+  std::vector<Json::Value> tracks(ReadValues(content, "track"));
+  std::vector<Json::Value>::const_iterator iter =
+      tracks.begin();
+  std::vector<Json::Value>::const_iterator iter_end =
+      tracks.end();
+  for (; iter != iter_end; ++iter) {
+    uint32 ssrc;
+    std::string label;
+    std::string cname;
+    std::string stream_label;
+    if (!GetUIntFromJsonObject(*iter, "ssrc", &ssrc))
+        return false;
+    // label is optional, it will be empty string if doesn't exist
+    GetStringFromJsonObject(*iter, "label", &label);
+    if (!GetStringFromJsonObject(*iter, "cname", &cname))
+        return false;
+    // stream_label is optional, it will be the same as cname if it
+    // doesn't exist.
+    GetStringFromJsonObject(*iter, "stream_label", &stream_label);
+    if (stream_label.empty())
+      stream_label = cname;
+    cricket::StreamParams stream;
+    stream.name = label;
+    stream.cname = cname;
+    stream.sync_label = stream_label;
+    stream.ssrcs.push_back(ssrc);
+    content_desc->AddStream(stream);
+  }
+  return true;
+}
+
 std::vector<Json::Value> ReadValues(
     const Json::Value& value, const std::string& key) {
   std::vector<Json::Value> objects;
@@ -370,27 +536,11 @@
   return objects;
 }
 
-Json::Value ReadValue(const Json::Value& value, const std::string& key) {
-  return value[key];
-}
-
-std::string ReadString(const Json::Value& value, const std::string& key) {
-  return value[key].asString();
-}
-
-uint32 ReadUInt(const Json::Value& value, const std::string& key) {
-  return value[key].asUInt();
-}
-
-double ReadDouble(const Json::Value& value, const std::string& key) {
-  return value[key].asDouble();
-}
-
 void Append(Json::Value* object, const std::string& key, bool value) {
   (*object)[key] = Json::Value(value);
 }
 
-void Append(Json::Value* object, const std::string& key, char * value) {
+void Append(Json::Value* object, const std::string& key, const char* value) {
   (*object)[key] = Json::Value(value);
 }
 
diff --git a/talk/app/webrtc/webrtcjson.h b/talk/app/webrtc/webrtcjson.h
index 906326d..069f4b6 100644
--- a/talk/app/webrtc/webrtcjson.h
+++ b/talk/app/webrtc/webrtcjson.h
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * 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:
@@ -29,80 +29,33 @@
 #define TALK_APP_WEBRTC_WEBRTCJSON_H_
 
 #include <string>
+#include <vector>
 
 #ifdef WEBRTC_RELATIVE_PATH
 #include "json/json.h"
 #else
 #include "third_party/jsoncpp/json.h"
 #endif
-#include "talk/p2p/base/candidate.h"
-#include "talk/session/phone/codec.h"
 
-namespace Json {
-class Value;
-}
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/sessiondescription.h"
 
 namespace cricket {
-class AudioContentDescription;
-class VideoContentDescription;
-struct ContentInfo;
 class SessionDescription;
 }
 
 namespace webrtc {
 
-std::vector<Json::Value> ReadValues(const Json::Value& value,
-                                    const std::string& key);
-
-bool BuildMediaMessage(
-    const cricket::ContentInfo& content_info,
-    const std::vector<cricket::Candidate>& candidates,
-    bool video,
-    Json::Value* value);
-
-bool GetJsonSignalingMessage(
+void JsonSerializeSessionDescription(
     const cricket::SessionDescription* sdp,
     const std::vector<cricket::Candidate>& candidates,
-    std::string* signaling_message);
+    Json::Value* media);
 
-bool BuildRtpMapParams(
-    const cricket::ContentInfo& audio_offer,
-    bool video,
-    std::vector<Json::Value>* rtpmap);
+bool JsonDeserializeSessionDescription(
+    const Json::Value& message,
+    cricket::SessionDescription* sdp,
+    std::vector<cricket::Candidate>* candidates);
 
-bool BuildAttributes(const std::vector<cricket::Candidate>& candidates,
-                     bool video,
-                     std::vector<Json::Value>* jcandidates);
-
-std::string Serialize(const Json::Value& value);
-bool Deserialize(const std::string& message, Json::Value& value);
-
-bool ParseJsonSignalingMessage(const std::string& signaling_message,
-                               cricket::SessionDescription** sdp,
-                               std::vector<cricket::Candidate>* candidates);
-bool ParseRtcpMux(const Json::Value& value);
-bool ParseAudioCodec(const Json::Value& value,
-                     cricket::AudioContentDescription* content);
-bool ParseVideoCodec(const Json::Value& value,
-                     cricket::VideoContentDescription* content);
-bool ParseIceCandidates(const Json::Value& value,
-                        std::vector<cricket::Candidate>* candidates);
-Json::Value ReadValue(const Json::Value& value, const std::string& key);
-std::string ReadString(const Json::Value& value, const std::string& key);
-double ReadDouble(const Json::Value& value, const std::string& key);
-uint32 ReadUInt(const Json::Value& value, const std::string& key);
-
-void Append(Json::Value* object, const std::string& key, bool value);
-void Append(Json::Value* object, const std::string& key, char * value);
-void Append(Json::Value* object, const std::string& key, int value);
-void Append(Json::Value* object, const std::string& key,
-            const std::string& value);
-void Append(Json::Value* object, const std::string& key, uint32 value);
-void Append(Json::Value* object, const std::string& key,
-            const Json::Value& value);
-void Append(Json::Value* object,
-            const std::string& key,
-            const std::vector<Json::Value>& values);
-}
+}  // namespace webrtc
 
 #endif  // TALK_APP_WEBRTC_WEBRTCJSON_H_
diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc
new file mode 100644
index 0000000..bdf4d00
--- /dev/null
+++ b/talk/app/webrtc/webrtcsdp.cc
@@ -0,0 +1,776 @@
+/*
+ * 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/app/webrtc/webrtcsdp.h"
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/udpport.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/mediasessionclient.h"
+
+using cricket::AudioContentDescription;
+using cricket::Candidate;
+using cricket::ContentDescription;
+using cricket::CryptoParams;
+using cricket::MediaContentDescription;
+using cricket::MediaType;
+using cricket::StreamParams;
+using cricket::VideoContentDescription;
+using talk_base::SocketAddress;
+
+namespace webrtc {
+
+// Line prefix
+static const int kLinePrefixLength = 2;
+static const char kLinePrefixVersion[] = "v=";
+static const char kLinePrefixOrigin[] = "o=";
+static const char kLinePrefixSessionName[] = "s=";
+static const char kLinePrefixSessionInfo[] = "i=";
+static const char kLinePrefixSessionUri[] = "u=";
+static const char kLinePrefixSessionEmail[] = "e=";
+static const char kLinePrefixSessionPhone[] = "p=";
+static const char kLinePrefixSessionConnection[] = "c=";
+static const char kLinePrefixSessionBandwidth[] = "b=";
+static const char kLinePrefixTiming[] = "t=";
+static const char kLinePrefixRepeatTimes[] = "r=";
+static const char kLinePrefixTimeZone[] = "z=";
+static const char kLinePrefixEncryptionKey[] = "k=";
+static const char kLinePrefixMedia[] = "m=";
+static const char kLinePrefixAttributes[] = "a=";
+
+// Attributes
+static const char kAttributeMid[] = "mid:";
+static const char kAttributeRtcpMux[] = "rtcp-mux";
+static const char kAttributeSsrc[] = "ssrc:";
+static const char kAttributeCname[] = "cname:";
+static const char kAttributeMslabel[] = "mslabel:";
+static const char kAttributeLabel[] = "label:";
+static const char kAttributeCrypto[] = "crypto:";
+static const char kAttributeCandidate[] = "candidate:";
+static const char kAttributeCandidateTyp[] = "typ";
+static const char kAttributeCandidateName[] = "name";
+static const char kAttributeCandidateNetworkName[] = "network_name";
+static const char kAttributeCandidateUsername[] = "username";
+static const char kAttributeCandidatePassword[] = "password";
+static const char kAttributeCandidateGeneration[] = "generation";
+static const char kAttributeRtpmap[] = "rtpmap:";
+
+// Candidate
+static const char kCandidateHost[] = "host";
+static const char kCandidateSrflx[] = "srflx";
+// TODO: How to map the prflx with circket candidate type
+// static const char kCandidatePrflx[] = "prflx";
+static const char kCandidateRelay[] = "relay";
+
+static const char kSdpDelimiter = ' ';
+static const char kLineBreak[] = "\r\n";
+
+// TODO: Generate the Session and Time description
+// instead of hardcoding.
+static const char kSessionVersion[] = "v=0";
+static const char kSessionOrigin[] = "o=- 0 0 IN IP4 127.0.0.1";
+static const char kSessionName[] = "s=";
+static const char kTimeDescription[] = "t=0 0";
+static const char kAttrGroup[] = "a=group:BUNDLE audio video";
+static const int kIceComponent = 1;
+static const int kIceFoundation = 1;
+static const char kMediaTypeVideo[] = "video";
+static const char kMediaTypeAudio[] = "audio";
+
+// Default Video resolution.
+// TODO: Implement negotiation of video resolution.
+static const int kDefaultVideoWidth = 640;
+static const int kDefaultVideoHeight = 480;
+static const int kDefaultVideoFrameRate = 30;
+static const int kDefaultVideoPreference = 0;
+
+static void BuildMediaDescription(const cricket::ContentInfo& content_info,
+                                  const MediaType media_type,
+                                  std::string* message);
+static void BuildRtpMap(const MediaContentDescription* media_desc,
+                        const MediaType media_type,
+                        std::string* message);
+static void BuildCandidate(const std::vector<Candidate>& candidates,
+                           const MediaType media_type,
+                           std::string* message);
+
+static bool ParseSessionDescription(const std::string& message, size_t* pos);
+static bool ParseTimeDescription(const std::string& message, size_t* pos);
+static bool ParseMediaDescription(const std::string& message, size_t* pos,
+                                  cricket::SessionDescription* desc);
+static bool ParseContent(const std::string& message,
+                         const MediaType media_type,
+                         size_t* pos,
+                         ContentDescription* content);
+static bool ParseCandidates(const std::string& message,
+                            std::vector<Candidate>* candidates);
+
+// Helper functions
+#define LOG_PREFIX_PARSING_ERROR(line_prefix) LOG(LS_ERROR) \
+    << "Failed to parse the \"" << line_prefix << "\" line";
+
+#define LOG_LINE_PARSING_ERROR(line) LOG(LS_ERROR) \
+    << "Failed to parse line:" << line;
+
+static bool AddLine(const std::string& line, std::string* message) {
+  if (!message)
+    return false;
+
+  message->append(line);
+  message->append(kLineBreak);
+  return true;
+}
+
+static bool GetLine(const std::string& message,
+                    size_t* pos,
+                    std::string* line) {
+  size_t line_begin = *pos;
+  size_t line_end = message.find('\n', line_begin);
+  if (line_end == std::string::npos) {
+    return false;
+  }
+  // Update the new start position
+  *pos = line_end + 1;
+  if (line_end > 0 && (message.at(line_end - 1) == '\r')) {
+    --line_end;
+  }
+  *line = message.substr(line_begin, (line_end - line_begin));
+  return true;
+}
+
+static bool GetLineWithPrefix(const std::string& message, size_t* pos,
+                              std::string* line, const char* type) {
+  if (message.compare(*pos, kLinePrefixLength, type) != 0) {
+    return false;
+  }
+
+  if (!GetLine(message, pos, line))
+    return false;
+
+  return true;
+}
+
+static bool HasPrefix(const std::string& line,
+                      const std::string& prefix,
+                      size_t pos) {
+  return (line.compare(pos, prefix.size(), prefix) == 0);
+}
+
+static bool HasPrefix(const std::string& line,
+                      const std::string& prefix) {
+  return HasPrefix(line, prefix, 0);
+}
+
+static bool HasAttribute(const std::string& line,
+                         const std::string& attribute) {
+  return (line.compare(kLinePrefixLength, attribute.size(), attribute) == 0);
+}
+
+std::string SdpSerialize(const cricket::SessionDescription& desc,
+                         const std::vector<Candidate>& candidates) {
+  return SdpFormat(SdpSerializeSessionDescription(desc),
+                   SdpSerializeCandidates(candidates));
+}
+
+std::string SdpSerializeSessionDescription(
+    const cricket::SessionDescription& desc) {
+  std::string message;
+
+  // Session Description.
+  AddLine(kSessionVersion, &message);
+  AddLine(kSessionOrigin, &message);
+  AddLine(kSessionName, &message);
+
+  // Time Description.
+  AddLine(kTimeDescription, &message);
+
+  const cricket::ContentInfo* audio_content = GetFirstAudioContent(&desc);
+  const cricket::ContentInfo* video_content = GetFirstVideoContent(&desc);
+
+  // Group
+  if (audio_content && video_content)
+    AddLine(kAttrGroup, &message);
+
+  // Media Description
+  if (audio_content) {
+    BuildMediaDescription(*audio_content, cricket::MEDIA_TYPE_AUDIO, &message);
+  }
+
+  if (video_content) {
+    BuildMediaDescription(*video_content, cricket::MEDIA_TYPE_VIDEO, &message);
+  }
+
+  return message;
+}
+
+std::string SdpSerializeCandidates(const std::vector<Candidate>& candidates) {
+  std::string message;
+  // rfc5245
+  // a=candidate:<foundation> <component-id> <transport> <priority>
+  // <connection-address> <port> typ <candidate-types>
+  // [raddr <connection-address>] [rport <port>]
+  BuildCandidate(candidates, cricket::MEDIA_TYPE_AUDIO, &message);
+  BuildCandidate(candidates, cricket::MEDIA_TYPE_VIDEO, &message);
+  return message;
+}
+
+std::string SdpFormat(const std::string& desc, const std::string& candidates) {
+  std::string sdp;  // New sdp message.
+
+  std::vector<Candidate> candidates_vector;
+  if (!ParseCandidates(candidates, &candidates_vector))
+    return sdp;
+
+  size_t pos = 0;
+  std::string line;
+  while (GetLine(desc, &pos, &line)) {
+    AddLine(line, &sdp);  // Copy old line to new sdp.
+    if (!HasPrefix(line, kLinePrefixMedia)) {
+      continue;  // Loop until the next m line.
+    }
+    if (HasAttribute(line, kMediaTypeVideo)) {
+      BuildCandidate(candidates_vector, cricket::MEDIA_TYPE_VIDEO, &sdp);
+    } else if (HasAttribute(line, kMediaTypeAudio)) {
+      BuildCandidate(candidates_vector, cricket::MEDIA_TYPE_AUDIO, &sdp);
+    }
+  }
+
+  return sdp;
+}
+
+
+bool SdpDeserialize(const std::string& message,
+                    cricket::SessionDescription* desc,
+                    std::vector<Candidate>* candidates) {
+  return SdpDeserializeSessionDescription(message, desc) &&
+         SdpDeserializeCandidates(message, candidates);
+}
+
+bool SdpDeserializeSessionDescription(const std::string& message,
+                                      cricket::SessionDescription* desc) {
+  size_t current_pos = 0;
+
+  // Session Description
+  if (!ParseSessionDescription(message, &current_pos)) {
+    return false;
+  }
+
+  // Time Description
+  if (!ParseTimeDescription(message, &current_pos)) {
+    return false;
+  }
+
+  // Media Description
+  if (!ParseMediaDescription(message, &current_pos, desc)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool SdpDeserializeCandidates(const std::string& message,
+                              std::vector<Candidate>* candidates) {
+  return ParseCandidates(message, candidates);
+}
+
+void BuildMediaDescription(const cricket::ContentInfo& content_info,
+                           const MediaType media_type,
+                           std::string* message) {
+  ASSERT(message != NULL);
+  // TODO: Rethink if we should use sprintfn instead of stringstream.
+  // According to the style guide, streams should only be used for logging.
+  // http://google-styleguide.googlecode.com/svn/
+  // trunk/cppguide.xml?showone=Streams#Streams
+  std::ostringstream os;
+  const MediaContentDescription* media_desc =
+      static_cast<const MediaContentDescription*> (
+          content_info.description);
+  ASSERT(media_desc != NULL);
+
+  // m=<media> <port> <proto> <fmt>
+  // fmt is a list of payload type numbers that MAY be used in the session.
+  const char* type = NULL;
+  if (media_type == cricket::MEDIA_TYPE_AUDIO)
+    type = kMediaTypeAudio;
+  else if (media_type == cricket::MEDIA_TYPE_VIDEO)
+    type = kMediaTypeVideo;
+  else
+    ASSERT(false);
+
+  std::string fmt;
+  if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+    const VideoContentDescription* video_desc =
+        static_cast<const VideoContentDescription*>(media_desc);
+    for (std::vector<cricket::VideoCodec>::const_iterator it =
+             video_desc->codecs().begin();
+         it != video_desc->codecs().end(); ++it) {
+      fmt.append(" ");
+      fmt.append(talk_base::ToString<int>(it->id));
+    }
+  } else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    const AudioContentDescription* audio_desc =
+        static_cast<const AudioContentDescription*>(media_desc);
+    for (std::vector<cricket::AudioCodec>::const_iterator it =
+             audio_desc->codecs().begin();
+         it != audio_desc->codecs().end(); ++it) {
+      fmt.append(" ");
+      fmt.append(talk_base::ToString<int>(it->id));
+    }
+  }
+  const int port = 0;
+  const char* proto = "RTP/AVPF";
+  os.str("");
+  os << kLinePrefixMedia << type << " " << port << " " << proto << fmt;
+  AddLine(os.str(), message);
+
+  // a=mid:<media>
+  os.str("");
+  os << kLinePrefixAttributes << kAttributeMid << type;
+  AddLine(os.str(), message);
+
+  // a=rtcp-mux
+  if (media_desc->rtcp_mux()) {
+    os.str("");
+    os << kLinePrefixAttributes << kAttributeRtcpMux;
+    AddLine(os.str(), message);
+  }
+
+  // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
+  for (std::vector<CryptoParams>::const_iterator it =
+           media_desc->cryptos().begin();
+       it != media_desc->cryptos().end(); ++it) {
+    os.str("");
+    os << kLinePrefixAttributes << kAttributeCrypto << it->tag << " "
+       << it->cipher_suite << " "
+       << it->key_params << " "
+       << it->session_params;
+    AddLine(os.str(), message);
+  }
+
+  // a=rtpmap:<payload type> <encoding name>/<clock rate>
+  // [/<encodingparameters>]
+  BuildRtpMap(media_desc, media_type, message);
+
+  // draft - Mechanisms for Media Source Selection in SDP
+  // a=ssrc:<ssrc-id> <attribute>:<value>
+  // a=ssrc:<ssrc-id> cname:<value> mslabel:<value> label:<value>
+  for (cricket::StreamParamsVec::const_iterator it =
+           media_desc->streams().begin();
+       it != media_desc->streams().end(); ++it) {
+    // Require that the track belongs to a media stream,
+    // ie the sync_label is set. This extra check is necessary since the
+    // MediaContentDescription always contains a streamparam with an ssrc even
+    // if no track or media stream have been created.
+    if (it->sync_label.empty()) continue;
+
+    os.str("");
+    os << kLinePrefixAttributes << kAttributeSsrc << it->ssrcs[0] << " "
+       << kAttributeCname << it->cname << " "
+       << kAttributeMslabel << it->sync_label << " "
+       << kAttributeLabel << it->name;
+    AddLine(os.str(), message);
+  }
+}
+
+void BuildRtpMap(const MediaContentDescription* media_desc,
+                 const MediaType media_type,
+                 std::string* message) {
+  ASSERT(message != NULL);
+  ASSERT(media_desc != NULL);
+  std::ostringstream os;
+  if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+    const VideoContentDescription* video_desc =
+        static_cast<const VideoContentDescription*>(media_desc);
+    for (std::vector<cricket::VideoCodec>::const_iterator it =
+             video_desc->codecs().begin();
+         it != video_desc->codecs().end(); ++it) {
+      // a=rtpmap:<payload type> <encoding name>/<clock rate>
+      // [/<encodingparameters>]
+      os.str("");
+      os << kLinePrefixAttributes << kAttributeRtpmap << it->id << " "
+         << it->name << "/" << 0;
+      AddLine(os.str(), message);
+    }
+  } else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    const AudioContentDescription* audio_desc =
+        static_cast<const AudioContentDescription*>(media_desc);
+    for (std::vector<cricket::AudioCodec>::const_iterator it =
+             audio_desc->codecs().begin();
+         it != audio_desc->codecs().end(); ++it) {
+      // a=rtpmap:<payload type> <encoding name>/<clock rate>
+      // [/<encodingparameters>]
+      os.str("");
+      os << kLinePrefixAttributes << kAttributeRtpmap << it->id << " "
+         << it->name << "/" << it->clockrate;
+      AddLine(os.str(), message);
+    }
+  }
+}
+
+void BuildCandidate(const std::vector<Candidate>& candidates,
+                    const MediaType media_type,
+                    std::string* message) {
+  std::ostringstream os;
+  for (std::vector<Candidate>::const_iterator it = candidates.begin();
+       it != candidates.end(); ++it) {
+    // a=candidate:<foundation> <component-id> <transport> <priority>
+    // <connection-address> <port> typ <candidate-types>
+    // [raddr <connection-address>] [rport <port>]
+    // *(SP extension-att-name SP extension-att-value)
+    if (((media_type == cricket::MEDIA_TYPE_VIDEO) &&
+         (it->name() == "video_rtcp" || it->name() == "video_rtp")) ||
+        ((media_type == cricket::MEDIA_TYPE_AUDIO) &&
+         (it->name() == "rtp" || it->name() == "rtcp"))) {
+      std::string type;
+      // Map the cricket candidate type to "host" / "srflx" / "prflx" / "relay"
+      if (it->type() == cricket::LOCAL_PORT_TYPE) {
+        type = kCandidateHost;
+      } else if (it->type() == cricket::STUN_PORT_TYPE) {
+        type = kCandidateSrflx;
+      } else if (it->type() == cricket::RELAY_PORT_TYPE) {
+        type = kCandidateRelay;
+      } else {
+        ASSERT(false);
+      }
+      os.str("");
+      os << kLinePrefixAttributes << kAttributeCandidate
+         << kIceFoundation << " " << kIceComponent << " "
+         << it->protocol() << " " << it->preference_str() << " "
+         << it->address().IPAsString() << " "
+         << it->address().PortAsString() << " "
+         << kAttributeCandidateTyp << " " << type << " "
+         << kAttributeCandidateName << " " << it->name() << " "
+         << kAttributeCandidateNetworkName << " " << it->network_name() << " "
+         << kAttributeCandidateUsername << " " << it->username() << " "
+         << kAttributeCandidatePassword << " " << it->password() << " "
+         << kAttributeCandidateGeneration << " " << it->generation();
+      AddLine(os.str(), message);
+    }
+  }
+}
+
+bool ParseSessionDescription(const std::string& message, size_t* pos) {
+  std::string line;
+
+  // v=  (protocol version)
+  if (!GetLineWithPrefix(message, pos, &line, kLinePrefixVersion)) {
+    LOG_PREFIX_PARSING_ERROR(kLinePrefixVersion);
+    return false;
+  }
+  // o=  (originator and session identifier)
+  if (!GetLineWithPrefix(message, pos, &line, kLinePrefixOrigin)) {
+    LOG_PREFIX_PARSING_ERROR(kLinePrefixOrigin);
+    return false;
+  }
+  // s=  (session name)
+  if (!GetLineWithPrefix(message, pos, &line, kLinePrefixSessionName)) {
+    LOG_PREFIX_PARSING_ERROR(kLinePrefixSessionName);
+    return false;
+  }
+
+  // Optional lines
+  // Those are the optional lines, so shouldn't return false if not present.
+  // i=* (session information)
+  GetLineWithPrefix(message, pos, &line, kLinePrefixSessionInfo);
+
+  // u=* (URI of description)
+  GetLineWithPrefix(message, pos, &line, kLinePrefixSessionUri);
+
+  // e=* (email address)
+  GetLineWithPrefix(message, pos, &line, kLinePrefixSessionEmail);
+
+  // p=* (phone number)
+  GetLineWithPrefix(message, pos, &line, kLinePrefixSessionPhone);
+
+  // c=* (connection information -- not required if included in
+  //      all media)
+  GetLineWithPrefix(message, pos, &line, kLinePrefixSessionConnection);
+
+  // b=* (zero or more bandwidth information lines)
+  while (GetLineWithPrefix(message, pos, &line, kLinePrefixSessionBandwidth)) {
+    // By pass zero or more b lines.
+  }
+
+  return true;
+}
+
+bool ParseTimeDescription(const std::string& message, size_t* pos) {
+  std::string line;
+  // One or more time descriptions ("t=" and "r=" lines; see below)
+  // t=  (time the session is active)
+  // r=* (zero or more repeat times)
+  // Ensure there's at least one time description
+  if (!GetLineWithPrefix(message, pos, &line, kLinePrefixTiming)) {
+    LOG_PREFIX_PARSING_ERROR(kLinePrefixTiming);
+    return false;
+  }
+
+  while (GetLineWithPrefix(message, pos, &line, kLinePrefixRepeatTimes)) {
+    // By pass zero or more r lines.
+  }
+
+  // Go through the rest of the time descriptions
+  while (GetLineWithPrefix(message, pos, &line, kLinePrefixTiming)) {
+    while (GetLineWithPrefix(message, pos, &line, kLinePrefixRepeatTimes)) {
+      // By pass zero or more r lines.
+    }
+  }
+
+  // z=* (time zone adjustments)
+  GetLineWithPrefix(message, pos, &line, kLinePrefixTimeZone);
+
+  // k=* (encryption key)
+  GetLineWithPrefix(message, pos, &line, kLinePrefixEncryptionKey);
+
+  // a=* (zero or more session attribute lines)
+  while (GetLineWithPrefix(message, pos, &line, kLinePrefixAttributes)) {
+    // TODO: parse the a=group:BUNDLE
+  }
+
+  return true;
+}
+
+bool ParseMediaDescription(const std::string& message, size_t* pos,
+                           cricket::SessionDescription* desc) {
+  ASSERT(desc != NULL);
+
+  std::string line;
+
+  // Zero or more media descriptions
+  // m=<media> <port> <proto> <fmt>
+  while (GetLineWithPrefix(message, pos, &line, kLinePrefixMedia)) {
+    MediaType media_type = cricket::MEDIA_TYPE_VIDEO;
+    ContentDescription* content = NULL;
+    if (HasAttribute(line, kMediaTypeVideo)) {
+      media_type = cricket::MEDIA_TYPE_VIDEO;
+      content = new VideoContentDescription();
+      desc->AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP, content);
+    } else if (HasAttribute(line, kMediaTypeAudio)) {
+      media_type = cricket::MEDIA_TYPE_AUDIO;
+      content = new AudioContentDescription();
+      desc->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP, content);
+    } else {
+      LOG(LS_WARNING) << "Unsupported media type: " << line;
+    }
+
+    if (!ParseContent(message, media_type, pos, content))
+      return false;
+  }
+  return true;
+}
+
+bool ParseContent(const std::string& message,
+                  const MediaType media_type,
+                  size_t* pos,
+                  ContentDescription* content) {
+  std::string line;
+  // Loop until the next m line
+  while (!HasPrefix(message, kLinePrefixMedia, *pos)) {
+    if (!GetLine(message, pos, &line)) {
+      if (*pos >= message.size())
+        return true;  // Done parsing
+      else
+        return false;
+    }
+
+    if (!content) {
+      // Unsupported media type, just skip it.
+      continue;
+    }
+
+    if (!HasPrefix(line, kLinePrefixAttributes)) {
+      // TODO: Handle other lines if needed.
+      continue;
+    }
+
+    MediaContentDescription* media_desc =
+        static_cast<MediaContentDescription*> (content);
+
+    std::vector<std::string> fields;
+    talk_base::split(line.substr(kLinePrefixLength), kSdpDelimiter, &fields);
+
+    if (HasAttribute(line, kAttributeMid)) {
+      continue;
+    } else if (HasAttribute(line, kAttributeRtcpMux)) {
+      media_desc->set_rtcp_mux(true);
+    } else if (HasAttribute(line, kAttributeSsrc)) {
+      // a=ssrc:<ssrc-id> cname:<value> mslabel:<value> label:<value>
+      uint32 ssrc = 0;
+      std::string cname;
+      std::string mslabel;
+      std::string label;
+      for (std::vector<std::string>::const_iterator it = fields.begin();
+           it != fields.end(); ++it) {
+        if (HasPrefix(*it, kAttributeSsrc)) {
+          ASSERT(it == fields.begin());
+          ssrc = talk_base::FromString<uint32>(
+                     it->substr(strlen(kAttributeSsrc)));
+        } else if (HasPrefix(*it, kAttributeCname)) {
+          cname = it->substr(strlen(kAttributeCname));
+        } else if (HasPrefix(*it, kAttributeMslabel)) {
+          mslabel = it->substr(strlen(kAttributeMslabel));
+        } else if (HasPrefix(*it, kAttributeLabel)) {
+          label = it->substr(strlen(kAttributeLabel));
+        }
+      }
+      StreamParams stream;
+      stream.name = label;
+      stream.cname = cname;
+      stream.sync_label = mslabel;
+      stream.ssrcs.push_back(ssrc);
+      media_desc->AddStream(stream);
+    } else if (HasAttribute(line, kAttributeCrypto)) {
+      // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
+      if (fields.size() < 3) {  // 3 mandatory fields
+        LOG_LINE_PARSING_ERROR(line);
+        return false;
+      }
+      int tag = talk_base::FromString<int>(
+          fields[0].substr(strlen(kAttributeCrypto)));
+      const std::string crypto_suite = fields[1];
+      const std::string key_params = fields[2];
+      media_desc->AddCrypto(CryptoParams(tag, crypto_suite, key_params, ""));
+    } else if (HasAttribute(line, kAttributeCandidate)) {
+      continue;  // Parse candidates separately.
+    } else if (HasAttribute(line, kAttributeRtpmap)) {
+      // a=rtpmap:<payload type> <encoding name>/<clock rate>
+      // [/<encodingparameters>]
+      // 2 mandatory fields
+      if (fields.size() < 2) {
+        LOG_LINE_PARSING_ERROR(line);
+        return false;
+      }
+      const int payload_type = talk_base::FromString<int>(
+          fields[0].substr(strlen(kAttributeRtpmap)));
+      const std::string encoder = fields[1];
+      const size_t pos = encoder.find("/");
+      if (pos == std::string::npos)
+        return false;
+      const std::string encoding_name = encoder.substr(0, pos);
+      const int clock_rate =
+          talk_base::FromString<int>(encoder.substr(pos + 1));
+      if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+        VideoContentDescription* video_desc =
+            static_cast<VideoContentDescription*>(media_desc);
+        // TODO: We will send resolution in SDP. For now, use VGA.
+        video_desc->AddCodec(cricket::VideoCodec(payload_type, encoding_name,
+                                                 kDefaultVideoWidth,
+                                                 kDefaultVideoHeight,
+                                                 kDefaultVideoFrameRate,
+                                                 kDefaultVideoPreference));
+      } else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+        AudioContentDescription* audio_desc =
+            static_cast<AudioContentDescription*>(media_desc);
+        audio_desc->AddCodec(cricket::AudioCodec(payload_type, encoding_name,
+                                                 clock_rate, 0, 0, 0));
+      }
+    } else {
+      LOG(LS_WARNING) << "Unsupported line: " << line;
+    }
+  }
+  return true;
+}
+
+bool ParseCandidates(const std::string& message,
+                     std::vector<Candidate>* candidates) {
+  ASSERT(candidates != NULL);
+  std::string line;
+  size_t pos = 0;
+
+  // Loop until the next attribute line.
+  while (GetLine(message, &pos, &line)) {
+    if (!HasPrefix(line, kLinePrefixAttributes) ||
+        !HasAttribute(line, kAttributeCandidate)) {
+      continue;  // Only parse candidates
+    }
+    std::vector<std::string> fields;
+    talk_base::split(line.substr(kLinePrefixLength), kSdpDelimiter, &fields);
+    // a=candidate:<foundation> <component-id> <transport> <priority>
+    // <connection-address> <port> typ <candidate-types>
+    // [raddr <connection-address>] [rport <port>]
+    // *(SP extension-att-name SP extension-att-value)
+    // 8 mandatory fields
+    if (fields.size() < 8 || (fields[6] != kAttributeCandidateTyp)) {
+      LOG_LINE_PARSING_ERROR(line);
+      return false;
+    }
+    const std::string transport = fields[2];
+    const float priority = talk_base::FromString<float>(fields[3]);
+    const std::string connection_address = fields[4];
+    const int port = talk_base::FromString<int>(fields[5]);
+    std::string candidate_type;
+    const std::string type = fields[7];
+    if (type == kCandidateHost) {
+      candidate_type = cricket::LOCAL_PORT_TYPE;
+    } else if (type == kCandidateSrflx) {
+      candidate_type = cricket::STUN_PORT_TYPE;
+    } else if (type == kCandidateRelay) {
+      candidate_type = cricket::RELAY_PORT_TYPE;
+    } else {
+      LOG(LS_ERROR) << "Unsupported candidate type from line: " << line;
+      return false;
+    }
+
+    // extension
+    std::string name;
+    std::string network_name;
+    std::string username;
+    std::string password;
+    uint32 generation = 0;
+    for (size_t i = 8; i < (fields.size() - 1); ++i) {
+      const std::string field = fields.at(i);
+      if (field == kAttributeCandidateName) {
+        name = fields.at(++i);
+      } else if (field == kAttributeCandidateNetworkName) {
+        network_name = fields.at(++i);
+      } else if (field == kAttributeCandidateUsername) {
+        username = fields.at(++i);
+      } else if (field == kAttributeCandidatePassword) {
+        password = fields.at(++i);
+      } else if (field == kAttributeCandidateGeneration) {
+        generation = talk_base::FromString<uint32>(fields.at(++i));
+      }
+    }
+
+    SocketAddress address(connection_address, port);
+    Candidate candidate(name, transport, address, priority, username,
+        password, candidate_type, network_name, generation);
+    candidates->push_back(candidate);
+  }
+  return true;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/webrtcsdp.h b/talk/app/webrtc/webrtcsdp.h
new file mode 100644
index 0000000..e5c1b12
--- /dev/null
+++ b/talk/app/webrtc/webrtcsdp.h
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+// This file contain functions for parsing and serializing SDP messages.
+// Related RFC/draft including:
+// * RFC 4566 - SDP
+// * RFC 5245 - ICE
+// * RFC 3388 - Grouping of Media Lines in SDP
+// * RFC 4568 - SDP Security Descriptions for Media Streams
+// * draft-lennox-mmusic-sdp-source-selection-02 -
+//   Mechanisms for Media Source Selection in SDP
+
+#ifndef TALK_APP_WEBRTC_WEBRTCSDP_H_
+#define TALK_APP_WEBRTC_WEBRTCSDP_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/p2p/base/candidate.h"
+
+namespace cricket {
+class SessionDescription;
+}
+
+namespace webrtc {
+
+// Serializes the passed in SessionDescription and Candidates to a SDP string.
+// desc - The SessionDescription object to be serialized.
+// candidates - The Set of Candidate objects to be serialized.
+// return - SDP string serialized from the arguments.
+std::string SdpSerialize(const cricket::SessionDescription& desc,
+                         const std::vector<cricket::Candidate>& candidates);
+
+// Serializes the passed in SessionDescription to a SDP string.
+// desc - The SessionDescription object to be serialized.
+std::string SdpSerializeSessionDescription(
+    const cricket::SessionDescription& desc);
+
+// Serializes the passed in Candidates to a SDP string.
+// candidates - The Set of Candidate objects to be serialized.
+std::string SdpSerializeCandidates(
+    const std::vector<cricket::Candidate>& candidates);
+
+// Deserializes the passed in SDP string to a SessionDescription and Candidates.
+// message - SDP string to be Deserialized.
+// desc - The SessionDescription object deserialized from the SDP string.
+// candidates - The set of Candidate deserialized from the SDP string.
+// return - true on success, false on failure.
+bool SdpDeserialize(const std::string& message,
+                    cricket::SessionDescription* desc,
+                    std::vector<cricket::Candidate>* candidates);
+
+// Deserializes the passed in SDP string to a SessionDescription.
+// Candidates are ignored.
+// message - SDP string to be Deserialized.
+// desc - The SessionDescription object deserialized from the SDP string.
+// return - true on success, false on failure.
+bool SdpDeserializeSessionDescription(const std::string& message,
+                                      cricket::SessionDescription* desc);
+
+// Deserializes the passed in SDP string to Candidates.
+// Only the candidates are parsed from the SDP string.
+// message - SDP string to be Deserialized.
+// candidates - The set of Candidate deserialized from the SDP string.
+// return - true on success, false on failure.
+bool SdpDeserializeCandidates(const std::string& message,
+                              std::vector<cricket::Candidate>* candidates);
+
+// Formats a correct SDP string by reformatting a session description and
+// candidates.
+std::string SdpFormat(const std::string& desc, const std::string& candidates);
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_WEBRTCSDP_H_
diff --git a/talk/app/webrtc/webrtcsdp_unittest.cc b/talk/app/webrtc/webrtcsdp_unittest.cc
new file mode 100644
index 0000000..dbc7ccd
--- /dev/null
+++ b/talk/app/webrtc/webrtcsdp_unittest.cc
@@ -0,0 +1,374 @@
+/*
+ * 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 <string>
+
+#include "talk/app/webrtc/webrtcsdp.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/session/phone/mediasession.h"
+
+typedef std::vector<cricket::Candidate> Candidates;
+using cricket::AudioCodec;
+using cricket::AudioContentDescription;
+using cricket::ContentInfo;
+using cricket::CryptoParams;
+using cricket::SessionDescription;
+using cricket::StreamParams;
+using cricket::VideoCodec;
+using cricket::VideoContentDescription;
+
+// Reference sdp string
+static const char kSdpFullString[] =
+    "v=0\r\n"
+    "o=- 0 0 IN IP4 127.0.0.1\r\n"
+    "s=\r\n"
+    "t=0 0\r\n"
+    "a=group:BUNDLE audio video\r\n"
+    "m=audio 0 RTP/AVPF 103 104\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1234 typ host name rtp network_name "
+    "eth0 username user_rtp password password_rtp generation 0\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1235 typ host name rtcp network_name "
+    "eth0 username user_rtcp password password_rtcp generation 0\r\n"
+    "a=mid:audio\r\n"
+    "a=rtcp-mux\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 \r\n"
+    "a=rtpmap:103 ISAC/16000\r\n"
+    "a=rtpmap:104 ISAC/32000\r\n"
+    "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 "
+    "label:local_audio_1\r\n"
+    "a=ssrc:4 cname:stream_2_cname mslabel:local_stream_2 "
+    "label:local_audio_2\r\n"
+    "m=video 0 RTP/AVPF 120\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1236 typ host name video_rtcp "
+    "network_name eth0 username user_video_rtcp password password_video_rtcp "
+    "generation 0\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1237 typ host name video_rtp "
+    "network_name eth0 username user_video_rtp password password_video_rtp "
+    "generation 0\r\n"
+    "a=mid:video\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 \r\n"
+    "a=rtpmap:120 VP8/0\r\n"
+    "a=ssrc:2 cname:stream_1_cname mslabel:local_stream_1 "
+    "label:local_video_1\r\n"
+    "a=ssrc:3 cname:stream_1_cname mslabel:local_stream_1 "
+    "label:local_video_2\r\n"
+    "a=ssrc:5 cname:stream_2_cname mslabel:local_stream_2 "
+    "label:local_video_3\r\n";
+
+// SDP reference string without the candidates.
+static const char kSdpString[] =
+    "v=0\r\n"
+    "o=- 0 0 IN IP4 127.0.0.1\r\n"
+    "s=\r\n"
+    "t=0 0\r\n"
+    "a=group:BUNDLE audio video\r\n"
+    "m=audio 0 RTP/AVPF 103 104\r\n"
+    "a=mid:audio\r\n"
+    "a=rtcp-mux\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 \r\n"
+    "a=rtpmap:103 ISAC/16000\r\n"
+    "a=rtpmap:104 ISAC/32000\r\n"
+    "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 "
+    "label:local_audio_1\r\n"
+    "a=ssrc:4 cname:stream_2_cname mslabel:local_stream_2 "
+    "label:local_audio_2\r\n"
+    "m=video 0 RTP/AVPF 120\r\n"
+    "a=mid:video\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 \r\n"
+    "a=rtpmap:120 VP8/0\r\n"
+    "a=ssrc:2 cname:stream_1_cname mslabel:local_stream_1 "
+    "label:local_video_1\r\n"
+    "a=ssrc:3 cname:stream_1_cname mslabel:local_stream_1 "
+    "label:local_video_2\r\n"
+    "a=ssrc:5 cname:stream_2_cname mslabel:local_stream_2 "
+    "label:local_video_3\r\n";
+
+// Candidates reference string.
+static const char kSdpCandidates[] =
+    "a=candidate:1 1 udp 1 127.0.0.1 1234 typ host name rtp network_name "
+    "eth0 username user_rtp password password_rtp generation 0\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1235 typ host name rtcp network_name "
+    "eth0 username user_rtcp password password_rtcp generation 0\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1236 typ host name video_rtcp "
+    "network_name eth0 username user_video_rtcp password password_video_rtcp "
+    "generation 0\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1237 typ host name video_rtp "
+    "network_name eth0 username user_video_rtp password password_video_rtp "
+    "generation 0\r\n";
+
+static const char kSdpDestroyer[] = "!@#$%^&";
+
+// MediaStream 1
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStream1Cname[] = "stream_1_cname";
+static const char kAudioTrackLabel1[] = "local_audio_1";
+static const uint32 kAudioTrack1Ssrc = 1;
+static const char kVideoTrackLabel1[] = "local_video_1";
+static const uint32 kVideoTrack1Ssrc = 2;
+static const char kVideoTrackLabel2[] = "local_video_2";
+static const uint32 kVideoTrack2Ssrc = 3;
+
+// MediaStream 2
+static const char kStreamLabel2[] = "local_stream_2";
+static const char kStream2Cname[] = "stream_2_cname";
+static const char kAudioTrackLabel2[] = "local_audio_2";
+static const uint32 kAudioTrack2Ssrc = 4;
+static const char kVideoTrackLabel3[] = "local_video_3";
+static const uint32 kVideoTrack3Ssrc = 5;
+
+class WebRtcSdpTest : public testing::Test {
+ public:
+  WebRtcSdpTest() {
+    // AudioContentDescription
+    talk_base::scoped_ptr<AudioContentDescription> audio(
+        new AudioContentDescription());
+    audio->set_rtcp_mux(true);
+    StreamParams audio_stream1;
+    audio_stream1.name = kAudioTrackLabel1;
+    audio_stream1.cname = kStream1Cname;
+    audio_stream1.sync_label = kStreamLabel1;
+    audio_stream1.ssrcs.push_back(kAudioTrack1Ssrc);
+    audio->AddStream(audio_stream1);
+    StreamParams audio_stream2;
+    audio_stream2.name = kAudioTrackLabel2;
+    audio_stream2.cname = kStream2Cname;
+    audio_stream2.sync_label = kStreamLabel2;
+    audio_stream2.ssrcs.push_back(kAudioTrack2Ssrc);
+    audio->AddStream(audio_stream2);
+    audio->AddCrypto(CryptoParams(1, "AES_CM_128_HMAC_SHA1_32",
+        "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32", ""));
+    audio->AddCodec(AudioCodec(103, "ISAC", 16000, 0, 0, 0));
+    audio->AddCodec(AudioCodec(104, "ISAC", 32000, 0, 0, 0));
+    desc_.AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+                     audio.release());
+
+    // VideoContentDescription
+    talk_base::scoped_ptr<VideoContentDescription> video(
+        new VideoContentDescription());
+    StreamParams video_stream1;
+    video_stream1.name = kVideoTrackLabel1;
+    video_stream1.cname = kStream1Cname;
+    video_stream1.sync_label = kStreamLabel1;
+    video_stream1.ssrcs.push_back(kVideoTrack1Ssrc);
+    video->AddStream(video_stream1);
+    StreamParams video_stream2;
+    video_stream2.name = kVideoTrackLabel2;
+    video_stream2.cname = kStream1Cname;
+    video_stream2.sync_label = kStreamLabel1;
+    video_stream2.ssrcs.push_back(kVideoTrack2Ssrc);
+    video->AddStream(video_stream2);
+    StreamParams video_stream3;
+    video_stream3.name = kVideoTrackLabel3;
+    video_stream3.cname = kStream2Cname;
+    video_stream3.sync_label = kStreamLabel2;
+    video_stream3.ssrcs.push_back(kVideoTrack3Ssrc);
+    video->AddStream(video_stream3);
+    video->AddCrypto(CryptoParams(1, "AES_CM_128_HMAC_SHA1_80",
+        "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32", ""));
+    video->AddCodec(VideoCodec(120, "VP8", 640, 480, 30, 0));
+    desc_.AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+                     video.release());
+
+    int port = 1234;
+    talk_base::SocketAddress address("127.0.0.1", port++);
+    cricket::Candidate candidate1("rtp", "udp", address, 1,
+        "user_rtp", "password_rtp", "local", "eth0", 0);
+    address.SetPort(port++);
+    cricket::Candidate candidate2("rtcp", "udp", address, 1,
+        "user_rtcp", "password_rtcp", "local", "eth0", 0);
+    address.SetPort(port++);
+    cricket::Candidate candidate3("video_rtcp", "udp", address, 1,
+        "user_video_rtcp", "password_video_rtcp", "local", "eth0", 0);
+    address.SetPort(port++);
+    cricket::Candidate candidate4("video_rtp", "udp", address, 1,
+        "user_video_rtp", "password_video_rtp", "local", "eth0", 0);
+
+    candidates_.push_back(candidate1);
+    candidates_.push_back(candidate2);
+    candidates_.push_back(candidate3);
+    candidates_.push_back(candidate4);
+  }
+
+  bool CompareSessionDescription(const SessionDescription& desc1,
+                                 const SessionDescription& desc2) {
+    const ContentInfo* ac1 = desc1.GetContentByName("audio");
+    const AudioContentDescription* acd1 =
+        static_cast<const AudioContentDescription*>(ac1->description);
+    const ContentInfo* vc1 = desc1.GetContentByName("video");
+    const VideoContentDescription* vcd1 =
+        static_cast<const VideoContentDescription*>(vc1->description);
+
+    const ContentInfo* ac2 = desc2.GetContentByName("audio");
+    const AudioContentDescription* acd2 =
+        static_cast<const AudioContentDescription*>(ac2->description);
+    const ContentInfo* vc2 = desc2.GetContentByName("video");
+    const VideoContentDescription* vcd2 =
+        static_cast<const VideoContentDescription*>(vc2->description);
+
+    // rtcp_mux
+    EXPECT_EQ(acd1->rtcp_mux(), acd2->rtcp_mux());
+    EXPECT_EQ(vcd1->rtcp_mux(), vcd2->rtcp_mux());
+
+    // cryptos
+    EXPECT_EQ(acd1->cryptos().size(), acd2->cryptos().size());
+    EXPECT_EQ(vcd1->cryptos().size(), vcd2->cryptos().size());
+    if (acd1->cryptos().size() != acd2->cryptos().size() ||
+        vcd1->cryptos().size() != vcd2->cryptos().size()) {
+      return false;
+    }
+    for (size_t i = 0; i< acd1->cryptos().size(); ++i) {
+      const CryptoParams c1 = acd1->cryptos().at(i);
+      const CryptoParams c2 = acd2->cryptos().at(i);
+      EXPECT_TRUE(c1.Matches(c2));
+    }
+    for (size_t i = 0; i< vcd1->cryptos().size(); ++i) {
+      const CryptoParams c1 = vcd1->cryptos().at(i);
+      const CryptoParams c2 = vcd2->cryptos().at(i);
+      EXPECT_TRUE(c1.Matches(c2));
+    }
+
+    // codecs
+    EXPECT_EQ(acd1->codecs().size(), acd2->codecs().size());
+    if (acd1->codecs().size() != acd2->codecs().size())
+      return false;
+    EXPECT_EQ(vcd1->codecs().size(), vcd2->codecs().size());
+    if (vcd1->codecs().size() != vcd2->codecs().size())
+      return false;
+    for (size_t i = 0; i< acd1->codecs().size(); ++i) {
+      const AudioCodec c1 = acd1->codecs().at(i);
+      const AudioCodec c2 = acd2->codecs().at(i);
+      EXPECT_TRUE(c1.Matches(c2));
+    }
+    for (size_t i = 0; i< vcd1->codecs().size(); ++i) {
+      const VideoCodec c1 = vcd1->codecs().at(i);
+      const VideoCodec c2 = vcd2->codecs().at(i);
+      EXPECT_TRUE(c1.Matches(c2));
+      EXPECT_EQ(c1.width, c2.width);
+      EXPECT_EQ(c1.height, c2.height);
+      EXPECT_EQ(c1.framerate, c2.framerate);
+    }
+
+    // streams
+    EXPECT_EQ(acd1->streams(), acd2->streams());
+    EXPECT_EQ(vcd1->streams(), vcd2->streams());
+
+    return true;
+  }
+
+  bool CompareCandidates(const Candidates& cs1, const Candidates& cs2) {
+    EXPECT_EQ(cs1.size(), cs2.size());
+    if (cs1.size() != cs2.size())
+      return false;
+    for (size_t i = 0; i< cs1.size(); ++i) {
+      const cricket::Candidate c1 = cs1.at(i);
+      const cricket::Candidate c2 = cs2.at(i);
+      EXPECT_TRUE(c1.IsEquivalent(c2));
+    }
+    return true;
+  }
+
+  bool ReplaceAndTryToParse(const char* search, const char* replace) {
+    SessionDescription desc;
+    std::vector<cricket::Candidate> candidates;
+    std::string sdp = kSdpFullString;
+    talk_base::replace_substrs(search, strlen(search), replace,
+        strlen(replace), &sdp);
+    return webrtc::SdpDeserialize(sdp, &desc, &candidates);
+  }
+
+ protected:
+  SessionDescription desc_;
+  Candidates candidates_;
+};
+
+TEST_F(WebRtcSdpTest, Serialize) {
+  std::string message = webrtc::SdpSerialize(desc_, candidates_);
+  LOG(LS_INFO) << "SDP: " << message;
+  EXPECT_EQ(std::string(kSdpFullString), message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescription) {
+  std::string message = webrtc::SdpSerializeSessionDescription(desc_);
+  EXPECT_EQ(std::string(kSdpString), message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeCandidates) {
+  std::string message = webrtc::SdpSerializeCandidates(candidates_);
+  EXPECT_EQ(std::string(kSdpCandidates), message);
+}
+
+TEST_F(WebRtcSdpTest, Deserialize) {
+  SessionDescription desc;
+  std::vector<cricket::Candidate> candidates;
+  // Deserialize
+  EXPECT_TRUE(webrtc::SdpDeserialize(kSdpFullString, &desc, &candidates));
+  // Verify
+  LOG(LS_INFO) << "SDP: " << webrtc::SdpSerialize(desc, candidates);
+  EXPECT_TRUE(CompareSessionDescription(desc_, desc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescription) {
+  SessionDescription desc;
+  EXPECT_TRUE(webrtc::SdpDeserializeSessionDescription(kSdpString, &desc));
+  EXPECT_TRUE(CompareSessionDescription(desc_, desc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeCandidates) {
+  std::vector<cricket::Candidate> candidates;
+  EXPECT_TRUE(webrtc::SdpDeserializeCandidates(kSdpCandidates, &candidates));
+  EXPECT_TRUE(CompareCandidates(candidates_, candidates));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeBrokenSdp) {
+  // Broken session description
+  EXPECT_EQ(false, ReplaceAndTryToParse("v=", kSdpDestroyer));
+  EXPECT_EQ(false, ReplaceAndTryToParse("o=", kSdpDestroyer));
+  EXPECT_EQ(false, ReplaceAndTryToParse("s=", kSdpDestroyer));
+  // Broken time description
+  EXPECT_EQ(false, ReplaceAndTryToParse("t=", kSdpDestroyer));
+
+  // No group line
+  EXPECT_EQ(true, ReplaceAndTryToParse("a=group:BUNDLE audio video\r\n", ""));
+  EXPECT_EQ(true, ReplaceAndTryToParse("a=mid:audio\r\n", ""));
+  EXPECT_EQ(true, ReplaceAndTryToParse("a=mid:video\r\n", ""));
+
+  // Broken media description
+  EXPECT_EQ(true, ReplaceAndTryToParse("video 0 RTP/AVPF", kSdpDestroyer));
+}
+
+TEST_F(WebRtcSdpTest, FormatSdp) {
+  std::string full_sdp = webrtc::SdpFormat(kSdpString, kSdpCandidates);
+  EXPECT_EQ(kSdpFullString, full_sdp);
+}
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
index de414c5..e08344c 100644
--- a/talk/app/webrtc/webrtcsession.cc
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * 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:
@@ -27,498 +27,408 @@
 
 #include "talk/app/webrtc/webrtcsession.h"
 
-#include <string>
-#include <vector>
-
-#include "talk/base/common.h"
-#include "talk/base/json.h"
-#include "talk/base/scoped_ptr.h"
-#include "talk/p2p/base/constants.h"
-#include "talk/p2p/base/sessiondescription.h"
-#include "talk/p2p/base/p2ptransport.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtc/peerconnectionsignaling.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
 #include "talk/session/phone/channel.h"
 #include "talk/session/phone/channelmanager.h"
-#include "talk/session/phone/mediasessionclient.h"
-#include "talk/session/phone/voicechannel.h"
+#include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/videocapturer.h"
+
+using cricket::MediaContentDescription;
 
 namespace webrtc {
 
 enum {
   MSG_CANDIDATE_TIMEOUT = 101,
+  MSG_CANDIDATE_DISCOVERY_TIMEOUT = 102,
 };
 
-static const int kAudioMonitorPollFrequency = 100;
-static const int kMonitorPollFrequency = 1000;
-
-// We allow 30 seconds to establish a connection; beyond that we consider
-// it an error
+// We allow 30 seconds to establish a connection, otherwise it's an error.
 static const int kCallSetupTimeout = 30 * 1000;
-// A loss of connectivity is probably due to the Internet connection going
-// down, and it might take a while to come back on wireless networks, so we
-// use a longer timeout for that.
-static const int kCallLostTimeout = 60 * 1000;
+static const int kCandidateDiscoveryTimeout = 2000;
 
-static const char kVideoStream[] = "video_rtp";
-static const char kAudioStream[] = "rtp";
+// TODO - These are magic string used by cricket::VideoChannel.
+// These should be moved to a common place.
+static const char kRtpVideoChannelStr[] = "video_rtp";
+static const char kRtcpVideoChannelStr[] = "video_rtcp";
 
-WebRtcSession::WebRtcSession(const std::string& id,
-                             bool incoming,
-                             cricket::PortAllocator* allocator,
-                             cricket::ChannelManager* channelmgr,
-                             talk_base::Thread* signaling_thread)
-    : BaseSession(signaling_thread, channelmgr->worker_thread(),
-                  allocator, id, "", !incoming),
-      transport_(NULL),
-      channel_manager_(channelmgr),
-      transports_writable_(false),
-      muted_(false),
-      camera_muted_(false),
-      setup_timeout_(kCallSetupTimeout),
-      signaling_thread_(signaling_thread),
-      incoming_(incoming),
-      port_allocator_(allocator),
-      desc_factory_(channel_manager_) {
+// Constants for setting the default encoder size.
+// TODO: Implement proper negotiation of video resolution.
+static const int kDefaultVideoCodecId = 100;
+static const int kDefaultVideoCodecFramerate = 30;
+static const char kDefaultVideoCodecName[] = "VP8";
+static const int kDefaultVideoCodecWidth = 640;
+static const int kDefaultVideoCodecHeight = 480;
+
+// MediaSessionDescriptionFactory always creates one StreamParams for
+// legacy reasons even if options don't contain any streams.
+// We need to remove it in that case since here options contains all streams
+// we want to send.
+// TODO: This is placed in the wrong file. Can we solve this problem by
+// adding a method MediaSessionDescriptionFactory::set_add_legacy_streams(false)
+// to prevent it from creating legacy streams.
+static void RemoveLegacyStreams(const cricket::MediaSessionOptions& options,
+                                cricket::SessionDescription* description) {
+  bool found_audio_stream = false;
+  bool found_video_stream = false;
+  for (cricket::MediaSessionOptions::Streams::const_iterator it =
+      options.streams.begin();
+       it != options.streams.end(); ++it) {
+    if (it->type == cricket::MEDIA_TYPE_AUDIO)
+      found_audio_stream = true;
+
+    if (it->type == cricket::MEDIA_TYPE_VIDEO)
+      found_video_stream = true;
+  }
+  if (found_audio_stream == false) {
+    const cricket::ContentInfo* audio_info =
+        cricket::GetFirstAudioContent(description);
+      if (audio_info) {
+        const cricket::MediaContentDescription* const_media_desc =
+            static_cast<const cricket::MediaContentDescription*>(
+            audio_info->description);
+        cricket::MediaContentDescription* media_desc =
+            const_cast<cricket::MediaContentDescription*> (const_media_desc);
+        media_desc->mutable_streams().clear();
+      }
+  }
+  if (found_video_stream == false) {
+    const cricket::ContentInfo* video_info =
+        cricket::GetFirstVideoContent(description);
+      if (video_info) {
+        const cricket::MediaContentDescription* const_media_desc =
+            static_cast<const cricket::MediaContentDescription*>(
+                video_info->description);
+        cricket::MediaContentDescription* media_desc =
+               const_cast<cricket::MediaContentDescription*> (const_media_desc);
+        media_desc->mutable_streams().clear();
+      }
+  }
+}
+
+WebRtcSession::WebRtcSession(cricket::ChannelManager* channel_manager,
+                             talk_base::Thread* signaling_thread,
+                             talk_base::Thread* worker_thread,
+                             cricket::PortAllocator* port_allocator)
+    : cricket::BaseSession(signaling_thread, worker_thread, port_allocator,
+          talk_base::ToString(talk_base::CreateRandomId()),
+          cricket::NS_JINGLE_RTP, true),
+      channel_manager_(channel_manager),
+      observer_(NULL),
+      session_desc_factory_(channel_manager),
+      offer_sent_(false) {
 }
 
 WebRtcSession::~WebRtcSession() {
-  RemoveAllStreams();
-  // TODO: Do we still need Terminate?
-  // if (state_ != STATE_RECEIVEDTERMINATE) {
-  //   Terminate();
-  // }
-  if (transport_) {
-    delete transport_;
-    transport_ = NULL;
+  Terminate();
+}
+
+bool WebRtcSession::Initialize() {
+  // By default SRTP-SDES is enabled in WebRtc.
+  set_secure_policy(cricket::SEC_REQUIRED);
+
+  const cricket::VideoCodec default_codec(kDefaultVideoCodecId,
+      kDefaultVideoCodecName, kDefaultVideoCodecWidth, kDefaultVideoCodecHeight,
+      kDefaultVideoCodecFramerate, 0);
+  channel_manager_->SetDefaultVideoEncoderConfig(
+      cricket::VideoEncoderConfig(default_codec));
+
+  return CreateChannels();
+}
+
+void WebRtcSession::Terminate() {
+  if (voice_channel_.get()) {
+    channel_manager_->DestroyVoiceChannel(voice_channel_.release());
+  }
+  if (video_channel_.get()) {
+    channel_manager_->DestroyVideoChannel(video_channel_.release());
   }
 }
 
-bool WebRtcSession::Initiate() {
-  if (signaling_thread_ == NULL)
+void WebRtcSession::set_secure_policy(
+    cricket::SecureMediaPolicy secure_policy) {
+  session_desc_factory_.set_secure(secure_policy);
+}
+
+bool WebRtcSession::CreateChannels() {
+  voice_channel_.reset(channel_manager_->CreateVoiceChannel(
+      this, cricket::CN_AUDIO, true));
+  if (!voice_channel_.get()) {
+    LOG(LS_ERROR) << "Failed to create voice channel";
     return false;
+  }
 
-  transport_ = CreateTransport();
-
-  if (transport_ == NULL)
+  video_channel_.reset(channel_manager_->CreateVideoChannel(
+      this, cricket::CN_VIDEO, true, voice_channel_.get()));
+  if (!video_channel_.get()) {
+    LOG(LS_ERROR) << "Failed to create video channel";
     return false;
+  }
 
-  transport_->set_allow_local_ips(true);
-
-  // start transports
-  transport_->SignalRequestSignaling.connect(
-      this, &WebRtcSession::OnRequestSignaling);
-  transport_->SignalCandidatesReady.connect(
-      this, &WebRtcSession::OnCandidatesReady);
-  transport_->SignalWritableState.connect(
-      this, &WebRtcSession::OnWritableState);
-  // Limit the amount of time that setting up a call may take.
-  StartTransportTimeout(kCallSetupTimeout);
+  // TransportProxies and TransportChannels will be created when
+  // CreateVoiceChannel and CreateVideoChannel are called.
+  // Try connecting all transport channels. This is necessary to generate
+  // ICE candidates.
+  SpeculativelyConnectAllTransportChannels();
+  signaling_thread()->PostDelayed(
+      kCandidateDiscoveryTimeout, this, MSG_CANDIDATE_DISCOVERY_TIMEOUT);
   return true;
 }
 
-cricket::Transport* WebRtcSession::CreateTransport() {
+// Enabling voice and video channel.
+void WebRtcSession::EnableChannels() {
+  if (!voice_channel_->enabled())
+    voice_channel_->Enable(true);
+
+  if (!video_channel_->enabled())
+    video_channel_->Enable(true);
+}
+
+void WebRtcSession::OnTransportRequestSignaling(
+    cricket::Transport* transport) {
   ASSERT(signaling_thread()->IsCurrent());
-  return new cricket::P2PTransport(
-      talk_base::Thread::Current(),
-      channel_manager_->worker_thread(), port_allocator());
-}
-
-bool WebRtcSession::CreateVoiceChannel(const std::string& stream_id) {
-  // RTCP disabled
-  cricket::VoiceChannel* voice_channel =
-      channel_manager_->CreateVoiceChannel(this, stream_id, true);
-  if (voice_channel == NULL) {
-    LOG(LERROR) << "Unable to create voice channel.";
-    return false;
-  }
-  StreamInfo* stream_info = new StreamInfo(stream_id);
-  stream_info->channel = voice_channel;
-  stream_info->video = false;
-  streams_.push_back(stream_info);
-  return true;
-}
-
-bool WebRtcSession::CreateVideoChannel(const std::string& stream_id) {
-  // RTCP disabled
-  cricket::VideoChannel* video_channel =
-      channel_manager_->CreateVideoChannel(this, stream_id, true, NULL);
-  if (video_channel == NULL) {
-    LOG(LERROR) << "Unable to create video channel.";
-    return false;
-  }
-  StreamInfo* stream_info = new StreamInfo(stream_id);
-  stream_info->channel = video_channel;
-  stream_info->video = true;
-  streams_.push_back(stream_info);
-  return true;
-}
-
-cricket::TransportChannel* WebRtcSession::CreateChannel(
-    const std::string& content_name,
-    const std::string& name) {
-  if (!transport_) {
-    return NULL;
-  }
-  std::string type;
-  if (content_name.compare(kVideoStream) == 0) {
-    type = cricket::NS_GINGLE_VIDEO;
-  } else {
-    type = cricket::NS_GINGLE_AUDIO;
-  }
-  cricket::TransportChannel* transport_channel =
-      transport_->CreateChannel(name, type);
-  ASSERT(transport_channel != NULL);
-  return transport_channel;
-}
-
-cricket::TransportChannel* WebRtcSession::GetChannel(
-    const std::string& content_name, const std::string& name) {
-  if (!transport_)
-    return NULL;
-
-  return transport_->GetChannel(name);
-}
-
-void WebRtcSession::DestroyChannel(
-    const std::string& content_name, const std::string& name) {
-  if (!transport_)
-    return;
-
-  transport_->DestroyChannel(name);
-}
-
-void WebRtcSession::OnMessage(talk_base::Message* message) {
-  switch (message->message_id) {
-    case MSG_CANDIDATE_TIMEOUT:
-      if (transport_->writable()) {
-        // This should never happen: The timout triggered even
-        // though a call was successfully set up.
-        ASSERT(false);
-      }
-      SignalFailedCall();
-      break;
-    default:
-      cricket::BaseSession::OnMessage(message);
-      break;
-  }
-}
-
-bool WebRtcSession::Connect() {
-  if (streams_.empty()) {
-    // nothing to initiate
-    return false;
-  }
-  // lets connect all the transport channels created before for this session
-  transport_->ConnectChannels();
-
-  // create an offer now. This is to call SetState
-  // Actual offer will be send when OnCandidatesReady callback received
-  cricket::SessionDescription* offer = CreateOffer();
-  set_local_description(offer);
-  SetState((incoming()) ? STATE_SENTACCEPT : STATE_SENTINITIATE);
-
-  // Enable all the channels
-  EnableAllStreams();
-  SetVideoCapture(true);
-  return true;
-}
-
-bool WebRtcSession::SetVideoRenderer(const std::string& stream_id,
-                                     cricket::VideoRenderer* renderer) {
-  bool ret = false;
-  StreamMap::iterator iter;
-  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
-    StreamInfo* stream_info = (*iter);
-    if (stream_info->stream_id.compare(stream_id) == 0) {
-      ASSERT(stream_info->channel != NULL);
-      ASSERT(stream_info->video);
-      cricket::VideoChannel* channel = static_cast<cricket::VideoChannel*>(
-          stream_info->channel);
-      ret = channel->SetRenderer(0, renderer);
-      break;
-    }
-  }
-  return ret;
-}
-
-bool WebRtcSession::SetVideoCapture(bool capture) {
-  channel_manager_->SetVideoCapture(capture);
-  return true;
-}
-
-bool WebRtcSession::RemoveStream(const std::string& stream_id) {
-  bool ret = false;
-  StreamMap::iterator iter;
-  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
-    StreamInfo* sinfo = (*iter);
-    if (sinfo->stream_id.compare(stream_id) == 0) {
-      if (!sinfo->video) {
-        cricket::VoiceChannel* channel = static_cast<cricket::VoiceChannel*> (
-            sinfo->channel);
-        channel->Enable(false);
-        // Note: If later the channel is used by multiple streams, then we
-        // should not destroy the channel until all the streams are removed.
-        channel_manager_->DestroyVoiceChannel(channel);
-      } else {
-        cricket::VideoChannel* channel = static_cast<cricket::VideoChannel*> (
-            sinfo->channel);
-        channel->Enable(false);
-        // Note: If later the channel is used by multiple streams, then we
-        // should not destroy the channel until all the streams are removed.
-        channel_manager_->DestroyVideoChannel(channel);
-      }
-      // channel and transport will be deleted in
-      // DestroyVoiceChannel/DestroyVideoChannel
-      streams_.erase(iter);
-      ret = true;
-      break;
-    }
-  }
-  if (!ret) {
-    LOG(LERROR) << "No streams found for stream id " << stream_id;
-    // TODO: trigger onError callback
-  }
-  return ret;
-}
-
-void WebRtcSession::EnableAllStreams() {
-  StreamMap::const_iterator i;
-  for (i = streams_.begin(); i != streams_.end(); ++i) {
-    cricket::BaseChannel* channel = (*i)->channel;
-    if (channel)
-      channel->Enable(true);
-  }
-}
-
-void WebRtcSession::RemoveAllStreams() {
-  SetState(STATE_RECEIVEDTERMINATE);
-
-  // signaling_thread_->Post(this, MSG_RTC_REMOVEALLSTREAMS);
-  // First build a list of streams to remove and then remove them.
-  // The reason we do this is that if we remove the streams inside the
-  // loop, a stream might get removed while we're enumerating and the iterator
-  // will become invalid (and we crash).
-  // streams_ entry will be removed from ChannelManager callback method
-  // DestroyChannel
-  std::vector<std::string> streams_to_remove;
-  StreamMap::iterator iter;
-  for (iter = streams_.begin(); iter != streams_.end(); ++iter)
-    streams_to_remove.push_back((*iter)->stream_id);
-
-  for (std::vector<std::string>::iterator i = streams_to_remove.begin();
-       i != streams_to_remove.end(); ++i) {
-    RemoveStream(*i);
-  }
-}
-
-bool WebRtcSession::HasStream(const std::string& stream_id) const {
-  StreamMap::const_iterator iter;
-  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
-    StreamInfo* sinfo = (*iter);
-    if (stream_id.compare(sinfo->stream_id) == 0) {
-      return true;
-    }
-  }
-  return false;
-}
-
-bool WebRtcSession::HasChannel(bool video) const {
-  StreamMap::const_iterator iter;
-  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
-    StreamInfo* sinfo = (*iter);
-    if (sinfo->video == video) {
-      return true;
-    }
-  }
-  return false;
-}
-
-bool WebRtcSession::HasAudioChannel() const {
-  return HasChannel(false);
-}
-
-bool WebRtcSession::HasVideoChannel() const {
-  return HasChannel(true);
-}
-
-void WebRtcSession::OnRequestSignaling(cricket::Transport* transport) {
   transport->OnSignalingReady();
 }
 
-void WebRtcSession::OnWritableState(cricket::Transport* transport) {
-  ASSERT(transport == transport_);
-  const bool transports_writable = transport_->writable();
-  if (transports_writable) {
-    if (transports_writable != transports_writable_) {
-      signaling_thread_->Clear(this, MSG_CANDIDATE_TIMEOUT);
-    } else {
-      // At one point all channels were writable and we had full connectivity,
-      // but then we lost it. Start the timeout again to kill the call if it
-      // doesn't come back.
-      StartTransportTimeout(kCallLostTimeout);
-    }
-    transports_writable_ = transports_writable;
+void WebRtcSession::OnTransportConnecting(cricket::Transport* transport) {
+  ASSERT(signaling_thread()->IsCurrent());
+  // start monitoring for the write state of the transport.
+  OnTransportWritable(transport);
+}
+
+void WebRtcSession::OnTransportWritable(cricket::Transport* transport) {
+  ASSERT(signaling_thread()->IsCurrent());
+  // If the transport is not in writable state, start a timer to monitor
+  // the state. If the transport doesn't become writable state in 30 seconds
+  // then we are assuming call can't be continued.
+  signaling_thread()->Clear(this, MSG_CANDIDATE_TIMEOUT);
+  if (transport->HasChannels() && !transport->writable()) {
+    signaling_thread()->PostDelayed(
+        kCallSetupTimeout, this, MSG_CANDIDATE_TIMEOUT);
   }
-  NotifyTransportState();
-  return;
 }
 
-void WebRtcSession::StartTransportTimeout(int timeout) {
-  talk_base::Thread::Current()->PostDelayed(timeout, this,
-                                            MSG_CANDIDATE_TIMEOUT,
-                                            NULL);
+void WebRtcSession::OnTransportCandidatesReady(
+    cricket::Transport* transport, const cricket::Candidates& candidates) {
+  ASSERT(signaling_thread()->IsCurrent());
+  // Any new candidates after offer is sent will be dropped here.
+  if (offer_sent_)
+    return;
+  InsertTransportCandidates(candidates);
 }
 
-void WebRtcSession::NotifyTransportState() {
+void WebRtcSession::SendCandidates() {
+  ASSERT(!local_candidates_.empty());
+  observer_->OnCandidatesReady(local_candidates_);
+  offer_sent_ = true;
 }
 
-bool WebRtcSession::OnInitiateMessage(
-    cricket::SessionDescription* offer,
-    const std::vector<cricket::Candidate>& candidates) {
-  if (!offer) {
-    LOG(LERROR) << "No SessionDescription from peer";
+void WebRtcSession::OnTransportChannelGone(cricket::Transport* transport,
+                                           const std::string& name) {
+  ASSERT(signaling_thread()->IsCurrent());
+}
+
+void WebRtcSession::OnMessage(talk_base::Message* msg) {
+  switch (msg->message_id) {
+    case MSG_CANDIDATE_TIMEOUT:
+      LOG(LS_ERROR) << "Transport is not in writable state.";
+      SignalError();
+      break;
+    case MSG_CANDIDATE_DISCOVERY_TIMEOUT:
+      SendCandidates();
+      break;
+    default:
+      break;
+  }
+}
+
+void WebRtcSession::InsertTransportCandidates(
+    const cricket::Candidates& candidates) {
+  for (cricket::Candidates::const_iterator citer = candidates.begin();
+       citer != candidates.end(); ++citer) {
+    local_candidates_.push_back(*citer);
+  }
+}
+
+bool WebRtcSession::SetCaptureDevice(const std::string& name,
+                                     cricket::VideoCapturer* camera) {
+  // should be called from a signaling thread
+  ASSERT(signaling_thread()->IsCurrent());
+
+  // TODO: Refactor this when there is support for multiple cameras.
+  const uint32 dummy_ssrc = 0;
+  if (!channel_manager_->SetVideoCapturer(camera, dummy_ssrc)) {
+    LOG(LS_ERROR) << "Failed to set capture device.";
     return false;
   }
 
-  // Get capabilities from offer before generating an answer to it.
-  cricket::MediaSessionOptions options;
-  if (GetFirstAudioContent(offer))
-    options.has_audio = true;
-  if (GetFirstVideoContent(offer))
-    options.has_video = true;
-
-  talk_base::scoped_ptr<cricket::SessionDescription> answer;
-  answer.reset(CreateAnswer(offer, options));
-
-  if (!answer.get()) {
+  // Start the capture
+  cricket::CaptureResult ret = channel_manager_->SetVideoCapture(true);
+  if (ret != cricket::CR_SUCCESS && ret != cricket::CR_PENDING) {
+    LOG(LS_ERROR) << "Failed to start the capture device.";
     return false;
   }
 
-  const cricket::ContentInfo* audio_content = GetFirstAudioContent(
-      answer.get());
-  const cricket::ContentInfo* video_content = GetFirstVideoContent(
-      answer.get());
+  return true;
+}
 
-  if (!audio_content && !video_content) {
-    return false;
+void WebRtcSession::SetLocalRenderer(const std::string& name,
+                                     cricket::VideoRenderer* renderer) {
+  ASSERT(signaling_thread()->IsCurrent());
+  // TODO: Fix SetLocalRenderer.
+  // video_channel_->SetLocalRenderer(0, renderer);
+}
+
+void WebRtcSession::SetRemoteRenderer(const std::string& name,
+                                      cricket::VideoRenderer* renderer) {
+  ASSERT(signaling_thread()->IsCurrent());
+
+  const cricket::ContentInfo* video_info =
+      cricket::GetFirstVideoContent(remote_description());
+  if (!video_info) {
+    LOG(LS_ERROR) << "Video not received in this call";
   }
 
-  bool ret = true;
-  if (audio_content) {
-    ret = !HasAudioChannel() &&
-          CreateVoiceChannel(audio_content->name);
-    if (!ret) {
-      LOG(LERROR) << "Failed to create voice channel for "
-                  << audio_content->name;
-      return false;
-    }
-  }
-
-  if (video_content) {
-    ret = !HasVideoChannel() &&
-          CreateVideoChannel(video_content->name);
-    if (!ret) {
-      LOG(LERROR) << "Failed to create video channel for "
-                  << video_content->name;
-      return false;
-    }
-  }
-  // Provide remote candidates to the transport
-  transport_->OnRemoteCandidates(candidates);
-
-  set_remote_description(offer);
-  SetState(STATE_RECEIVEDINITIATE);
-
-  transport_->ConnectChannels();
-  EnableAllStreams();
-
-  set_local_description(answer.release());
-
-  // AddStream called only once with Video label
-  if (video_content) {
-    SignalAddStream(video_content->name, true);
+  const cricket::MediaContentDescription* video_content =
+      static_cast<const cricket::MediaContentDescription*>(
+          video_info->description);
+  cricket::StreamParams stream;
+  if (cricket::GetStreamByNickAndName(video_content->streams(), "", name,
+                                      &stream)) {
+    video_channel_->SetRenderer(stream.first_ssrc(), renderer);
   } else {
-    SignalAddStream(audio_content->name, false);
+    // Allow that |stream| does not exist if renderer is null but assert
+    // otherwise.
+    VERIFY(renderer == NULL);
   }
-  SetState(STATE_SENTACCEPT);
-  return true;
 }
 
-bool WebRtcSession::OnRemoteDescription(
-    cricket::SessionDescription* desc,
-    const std::vector<cricket::Candidate>& candidates) {
-  if (state() == STATE_SENTACCEPT ||
-      state() == STATE_RECEIVEDACCEPT ||
-      state() == STATE_INPROGRESS) {
-    transport_->OnRemoteCandidates(candidates);
-    return true;
+cricket::SessionDescription* WebRtcSession::CreateOffer(
+    const cricket::MediaSessionOptions& options) {
+  if (!options.has_video) {
+    LOG(LS_WARNING) << "To receive video, has_video flag must be set to true";
+    return NULL;
   }
-  // Session description is always accepted.
-  set_remote_description(desc);
-  SetState(STATE_RECEIVEDACCEPT);
-  // Will trigger OnWritableState() if successful.
-  transport_->OnRemoteCandidates(candidates);
 
-  if (!incoming()) {
-    // Trigger OnAddStream callback at the initiator
-    const cricket::ContentInfo* video_content = GetFirstVideoContent(desc);
-    if (video_content && !SendSignalAddStream(true)) {
-      LOG(LERROR) << "Video stream unexpected in answer.";
-      return false;
-    } else {
-      const cricket::ContentInfo* audio_content = GetFirstAudioContent(desc);
-      if (audio_content && !SendSignalAddStream(false)) {
-        LOG(LERROR) << "Audio stream unexpected in answer.";
-        return false;
-      }
-    }
-  }
-  return true;
-}
+  cricket::SessionDescription* offer(
+      session_desc_factory_.CreateOffer(options, local_description()));
 
-// Send the SignalAddStream with the stream_id based on the content type.
-bool WebRtcSession::SendSignalAddStream(bool video) {
-  StreamMap::const_iterator iter;
-  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
-    StreamInfo* sinfo = (*iter);
-    if (sinfo->video == video) {
-      SignalAddStream(sinfo->stream_id, video);
-      return true;
-    }
-  }
-  return false;
-}
-
-cricket::SessionDescription* WebRtcSession::CreateOffer() {
-  cricket::MediaSessionOptions options;
-  options.has_audio = false;  // disable default option
-  StreamMap::const_iterator iter;
-  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
-    if ((*iter)->video) {
-      options.has_video = true;
-    } else {
-      options.has_audio = true;
-    }
-  }
-  return desc_factory_.CreateOffer(options);
+  // MediaSessionDescriptionFactory always creates one StreamParams for
+  // legacy reasons even if options don't contain any streams.
+  // We need to remove it in that case since here options contains all streams
+  // we want to send.
+  RemoveLegacyStreams(options, offer);
+  return offer;
 }
 
 cricket::SessionDescription* WebRtcSession::CreateAnswer(
     const cricket::SessionDescription* offer,
     const cricket::MediaSessionOptions& options) {
-  return desc_factory_.CreateAnswer(offer, options);
+  cricket::SessionDescription* answer(
+      session_desc_factory_.CreateAnswer(offer, options,
+                                         local_description()));
+  // MediaSessionDescriptionFactory always creates one StreamParams for
+  // legacy reasons even if options don't contain any streams.
+  // We need to remove it in that case since here options contains all streams
+  // we want to send.
+  RemoveLegacyStreams(options, answer);
+  return answer;
 }
 
-void WebRtcSession::SetError(Error error) {
-  BaseSession::SetError(error);
-}
-
-void WebRtcSession::OnCandidatesReady(
-    cricket::Transport* transport,
-    const std::vector<cricket::Candidate>& candidates) {
-  std::vector<cricket::Candidate>::const_iterator iter;
-  for (iter = candidates.begin(); iter != candidates.end(); ++iter) {
-    local_candidates_.push_back(*iter);
+void WebRtcSession::SetLocalDescription(const cricket::SessionDescription* desc,
+                                        cricket::ContentAction type) {
+  if (!VERIFY((type == cricket::CA_ANSWER &&
+               state() == STATE_RECEIVEDINITIATE) ||
+              (type == cricket::CA_OFFER &&
+               (state() != STATE_RECEIVEDINITIATE &&
+                state() != STATE_SENTINITIATE)))) {
+    LOG(LS_ERROR) << "SetLocalDescription called with action in wrong state, "
+                  << "action: " << type << " state: " << state();
+    return;
   }
-  SignalLocalDescription(local_description(), candidates);
+
+  set_local_description(desc);
+  if (type == cricket::CA_ANSWER) {
+    EnableChannels();
+    SetState(STATE_SENTACCEPT);
+  } else {
+    SetState(STATE_SENTINITIATE);
+  }
 }
-} /* namespace webrtc */
+
+void WebRtcSession::SetRemoteDescription(cricket::SessionDescription* desc,
+                                         cricket::ContentAction type) {
+  if (!VERIFY((type == cricket::CA_ANSWER &&
+               state() == STATE_SENTINITIATE) ||
+              (type == cricket::CA_OFFER &&
+               (state() != STATE_RECEIVEDINITIATE &&
+                state() != STATE_SENTINITIATE)))) {
+    LOG(LS_ERROR) << "SetRemoteDescription called with action in wrong state, "
+                  << "action: " << type << " state: " << state();
+    return;
+  }
+  set_remote_description(desc);
+
+  if (type  == cricket::CA_ANSWER) {
+    EnableChannels();
+    SetState(STATE_RECEIVEDACCEPT);
+  } else {
+    SetState(STATE_RECEIVEDINITIATE);
+  }
+}
+
+void WebRtcSession::SetRemoteCandidates(
+    const cricket::Candidates& candidates) {
+  // First partition the candidates for the proxies. During creation of channels
+  // we created CN_AUDIO (audio) and CN_VIDEO (video) proxies.
+  cricket::Candidates audio_candidates;
+  cricket::Candidates video_candidates;
+  for (cricket::Candidates::const_iterator citer = candidates.begin();
+       citer != candidates.end(); ++citer) {
+    if (((*citer).name().compare(kRtpVideoChannelStr) == 0) ||
+        ((*citer).name().compare(kRtcpVideoChannelStr)) == 0) {
+      // Candidate names for video rtp and rtcp channel
+      video_candidates.push_back(*citer);
+    } else {
+      // Candidates for audio rtp and rtcp channel
+      // Channel name will be "rtp" and "rtcp"
+      audio_candidates.push_back(*citer);
+    }
+  }
+
+  // TODO: Justins comment:This is bad encapsulation, suggest we add a
+  // helper to BaseSession to allow us to
+  // pass in candidates without touching the transport proxies.
+  if (!audio_candidates.empty()) {
+    cricket::TransportProxy* audio_proxy = GetTransportProxy(cricket::CN_AUDIO);
+    if (audio_proxy) {
+      // CompleteNegotiation will set actual impl's in Proxy.
+      if (!audio_proxy->negotiated())
+        audio_proxy->CompleteNegotiation();
+      // TODO - Add a interface to TransportProxy to accept
+      // remote candidate list.
+      audio_proxy->impl()->OnRemoteCandidates(audio_candidates);
+    } else {
+      LOG(LS_INFO) << "No audio TransportProxy exists";
+    }
+  }
+
+  if (!video_candidates.empty()) {
+    cricket::TransportProxy* video_proxy = GetTransportProxy(cricket::CN_VIDEO);
+    if (video_proxy) {
+      // CompleteNegotiation will set actual impl's in Proxy.
+      if (!video_proxy->negotiated())
+        video_proxy->CompleteNegotiation();
+      // TODO - Add a interface to TransportProxy to accept
+      // remote candidate list.
+      video_proxy->impl()->OnRemoteCandidates(video_candidates);
+    } else {
+      LOG(LS_INFO) << "No video TransportProxy exists";
+    }
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h
index 6338d25..3f8967e 100644
--- a/talk/app/webrtc/webrtcsession.h
+++ b/talk/app/webrtc/webrtcsession.h
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * 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:
@@ -28,178 +28,114 @@
 #ifndef TALK_APP_WEBRTC_WEBRTCSESSION_H_
 #define TALK_APP_WEBRTC_WEBRTCSESSION_H_
 
-#include <map>
 #include <string>
 #include <vector>
 
-#include "talk/base/logging.h"
-#include "talk/base/messagehandler.h"
-#include "talk/p2p/base/candidate.h"
+#include "talk/app/webrtc/mediastreamprovider.h"
+#include "talk/app/webrtc/sessiondescriptionprovider.h"
+#include "talk/app/webrtc/webrtcsessionobserver.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
 #include "talk/p2p/base/session.h"
-#include "talk/session/phone/channel.h"
-#include "talk/session/phone/mediachannel.h"
 #include "talk/session/phone/mediasession.h"
 
 namespace cricket {
+
 class ChannelManager;
 class Transport;
-class TransportChannel;
-class VoiceChannel;
+class VideoCapturer;
 class VideoChannel;
-struct ConnectionInfo;
-}
+class VoiceChannel;
 
-namespace Json {
-class Value;
-}
+}  // namespace cricket
 
 namespace webrtc {
 
-typedef std::vector<cricket::AudioCodec> AudioCodecs;
-typedef std::vector<cricket::VideoCodec> VideoCodecs;
-
-class WebRtcSession : public cricket::BaseSession {
+class WebRtcSession : public cricket::BaseSession,
+                      public MediaProviderInterface,
+                      public SessionDescriptionProvider {
  public:
-  WebRtcSession(const std::string& id,
-                    bool incoming,
-                    cricket::PortAllocator* allocator,
-                    cricket::ChannelManager* channelmgr,
-                    talk_base::Thread* signaling_thread);
+  WebRtcSession(cricket::ChannelManager* channel_manager,
+                talk_base::Thread* signaling_thread,
+                talk_base::Thread* worker_thread,
+                cricket::PortAllocator* port_allocator);
+  virtual ~WebRtcSession();
 
-  ~WebRtcSession();
+  bool Initialize();
 
-  bool Initiate();
-  bool Connect();
-  bool OnRemoteDescription(cricket::SessionDescription* sdp,
-      const std::vector<cricket::Candidate>& candidates);
-  bool OnInitiateMessage(cricket::SessionDescription* sdp,
-      const std::vector<cricket::Candidate>& candidates);
-  bool CreateVoiceChannel(const std::string& stream_id);
-  bool CreateVideoChannel(const std::string& stream_id);
-  bool RemoveStream(const std::string& stream_id);
-  void RemoveAllStreams();
-
-  // Returns true if we have either a voice or video stream matching this label.
-  bool HasStream(const std::string& label) const;
-  bool HasChannel(bool video) const;
-
-  // Returns true if there's one or more audio channels in the session.
-  bool HasAudioChannel() const;
-
-  // Returns true if there's one or more video channels in the session.
-  bool HasVideoChannel() const;
-
-  bool SetVideoRenderer(const std::string& stream_id,
-                        cricket::VideoRenderer* renderer);
-
-  // This signal occurs when all the streams have been removed.
-  // It is triggered by a successful call to the RemoveAllStream or
-  // the OnRemoteDescription with stream deleted signaling message with the
-  // candidates port equal to 0.
-  sigslot::signal1<WebRtcSession*> SignalRemoveStreamMessage;
-
-  // This signal indicates a stream has been added properly.
-  // It is triggered by a successful call to the OnInitiateMessage or
-  // the OnRemoteDescription and if it's going to the STATE_RECEIVEDACCEPT.
-  sigslot::signal2<const std::string&, bool> SignalAddStream;
-
-  // This signal occurs when one stream is removed with the signaling
-  // message from the remote peer with the candidates port equal to 0.
-  sigslot::signal2<const std::string&, bool> SignalRemoveStream;
-
-  // This signal occurs when the local candidate is ready
-  sigslot::signal2<const cricket::SessionDescription*,
-      const std::vector<cricket::Candidate>&> SignalLocalDescription;
-
-  // This signal triggers when setting up or resuming a call has not been
-  // successful before a certain time out.
-  sigslot::signal0<> SignalFailedCall;
-
-  bool muted() const { return muted_; }
-  bool camera_muted() const { return camera_muted_; }
-  const std::vector<cricket::Candidate>& local_candidates() {
-    return local_candidates_;
+  void RegisterObserver(WebRtcSessionObserver* observer) {
+    observer_ = observer;
   }
-  void set_incoming(bool incoming) { incoming_ = incoming; }
-  bool incoming() const { return incoming_; }
-  cricket::PortAllocator* port_allocator() const { return port_allocator_; }
-  talk_base::Thread* signaling_thread() const { return signaling_thread_; }
 
- protected:
-  // methods from cricket::BaseSession
-  virtual void SetError(cricket::BaseSession::Error error);
-  virtual cricket::TransportChannel* CreateChannel(
-      const std::string& content_name, const std::string& name);
-  virtual cricket::TransportChannel* GetChannel(
-      const std::string& content_name, const std::string& name);
-  virtual void DestroyChannel(
-      const std::string& content_name, const std::string& name);
+  const cricket::VoiceChannel* voice_channel() const {
+    return voice_channel_.get();
+  }
+  const cricket::VideoChannel* video_channel() const {
+    return video_channel_.get();
+  }
+
+  void set_secure_policy(cricket::SecureMediaPolicy secure_policy);
+  cricket::SecureMediaPolicy secure_policy() const {
+    return session_desc_factory_.secure();
+  }
+
+  // Generic error message callback from WebRtcSession.
+  // TODO - It may be necessary to supply error code as well.
+  sigslot::signal0<> SignalError;
+
+  // Implements SessionDescriptionProvider
+  virtual cricket::SessionDescription* CreateOffer(
+      const cricket::MediaSessionOptions& options);
+  virtual cricket::SessionDescription* CreateAnswer(
+      const cricket::SessionDescription* offer,
+      const cricket::MediaSessionOptions& options);
+  virtual void SetLocalDescription(const cricket::SessionDescription* desc,
+                                   cricket::ContentAction type);
+  virtual void SetRemoteDescription(cricket::SessionDescription* desc,
+                                    cricket::ContentAction type);
+  virtual void SetRemoteCandidates(
+      const std::vector<cricket::Candidate>& remote_candidates);
+  virtual const cricket::SessionDescription* local_description() const {
+    return cricket::BaseSession::local_description();
+  }
+  virtual const cricket::SessionDescription* remote_description() const {
+    return cricket::BaseSession::remote_description();
+  }
 
  private:
-  struct StreamInfo {
-    explicit StreamInfo(const std::string stream_id)
-        : channel(NULL),
-          video(false),
-          stream_id(stream_id) {}
+  // Implements MediaProviderInterface.
+  virtual bool SetCaptureDevice(const std::string& name,
+                                cricket::VideoCapturer* camera);
+  virtual void SetLocalRenderer(const std::string& name,
+                                cricket::VideoRenderer* renderer);
+  virtual void SetRemoteRenderer(const std::string& name,
+                                 cricket::VideoRenderer* renderer);
 
-    StreamInfo()
-        : channel(NULL),
-          video(false) {}
-    cricket::BaseChannel* channel;
-    bool video;
-    std::string stream_id;
-  };
-  // Not really a map (vector).
-  typedef std::vector<StreamInfo*> StreamMap;
+  // Transport related callbacks, override from cricket::BaseSession.
+  virtual void OnTransportRequestSignaling(cricket::Transport* transport);
+  virtual void OnTransportConnecting(cricket::Transport* transport);
+  virtual void OnTransportWritable(cricket::Transport* transport);
+  virtual void OnTransportCandidatesReady(
+      cricket::Transport* transport,
+      const cricket::Candidates& candidates);
+  virtual void OnTransportChannelGone(cricket::Transport* transport,
+                                      const std::string& name);
 
-  // methods signaled by the transport
-  void OnRequestSignaling(cricket::Transport* transport);
-  void OnCandidatesReady(cricket::Transport* transport,
-                         const std::vector<cricket::Candidate>& candidates);
-  void OnWritableState(cricket::Transport* transport);
-  void OnTransportError(cricket::Transport* transport);
-  void OnChannelGone(cricket::Transport* transport);
+  // Creates channels for voice and video.
+  bool CreateChannels();
+  void EnableChannels();
+  virtual void OnMessage(talk_base::Message* msg);
+  void InsertTransportCandidates(const cricket::Candidates& candidates);
+  void Terminate();
+  void SendCandidates();
 
-  bool CheckForStreamDeleteMessage(
-      const std::vector<cricket::Candidate>& candidates);
-  void ProcessTerminateAccept(cricket::SessionDescription* desc);
-
-  void UpdateTransportWritableState();
-  bool CheckAllTransportsWritable();
-  void StartTransportTimeout(int timeout);
-  void NotifyTransportState();
-
-  cricket::SessionDescription* CreateOffer();
-  cricket::SessionDescription* CreateAnswer(
-      const cricket::SessionDescription* answer,
-      const cricket::MediaSessionOptions& options);
-
-  // from MessageHandler
-  virtual void OnMessage(talk_base::Message* message);
-
-  virtual cricket::Transport* CreateTransport();
-  cricket::Transport* GetTransport();
-
-  typedef std::map<std::string, cricket::TransportChannel*> TransportChannelMap;
-
-  bool SetVideoCapture(bool capture);
-  void EnableAllStreams();
-  bool SendSignalAddStream(bool video);
-
-  cricket::Transport* transport_;
+  talk_base::scoped_ptr<cricket::VoiceChannel> voice_channel_;
+  talk_base::scoped_ptr<cricket::VideoChannel> video_channel_;
   cricket::ChannelManager* channel_manager_;
-  std::vector<StreamInfo*> streams_;
-  TransportChannelMap transport_channels_;
-  bool transports_writable_;
-  bool muted_;
-  bool camera_muted_;
-  int setup_timeout_;
-  std::vector<cricket::Candidate> local_candidates_;
-
-  talk_base::Thread* signaling_thread_;
-  bool incoming_;
-  cricket::PortAllocator* port_allocator_;
-  cricket::MediaSessionDescriptionFactory desc_factory_;
+  cricket::Candidates local_candidates_;
+  WebRtcSessionObserver* observer_;
+  cricket::MediaSessionDescriptionFactory session_desc_factory_;
+  bool offer_sent_;
 };
 
 }  // namespace webrtc
diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc
index 7a07614..9fb9b1d 100644
--- a/talk/app/webrtc/webrtcsession_unittest.cc
+++ b/talk/app/webrtc/webrtcsession_unittest.cc
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * 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:
@@ -25,383 +25,423 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <stdio.h>
-
-#include <list>
-
-#include "base/gunit.h"
-#include "base/helpers.h"
-#include "talk/app/webrtc/unittest_utilities.h"
 #include "talk/app/webrtc/webrtcsession.h"
+#include "talk/base/logging.h"
 #include "talk/base/fakenetwork.h"
-#include "talk/base/scoped_ptr.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/gunit.h"
+#include "talk/base/network.h"
+#include "talk/base/physicalsocketserver.h"
 #include "talk/base/thread.h"
-#include "talk/p2p/base/portallocator.h"
-#include "talk/p2p/base/sessiondescription.h"
-#include "talk/p2p/client/fakeportallocator.h"
-#include "talk/session/phone/fakesession.h"
-#include "talk/session/phone/mediasessionclient.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/stunserver.h"
+#include "talk/p2p/base/teststunserver.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/fakedevicemanager.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/mediasession.h"
 
-class WebRtcSessionTest
-    : public sigslot::has_slots<>,
-      public testing::Test {
+using talk_base::SocketAddress;
+static const SocketAddress kClientAddr1("11.11.11.11", 0);
+static const SocketAddress kClientAddr2("22.22.22.22", 0);
+static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
+
+static const char kStream1[] = "stream1";
+static const char kVideoTrack1[] = "video1";
+static const char kAudioTrack1[] = "audio1";
+
+static const char kStream2[] = "stream2";
+static const char kVideoTrack2[] = "video2";
+static const char kAudioTrack2[] = "audio2";
+
+
+class MockWebRtcSessionObserver : public webrtc::WebRtcSessionObserver {
  public:
-  enum CallbackId {
-    kNone,
-    kOnAddStream,
-    kOnRemoveStream,
-    kOnLocalDescription,
-    kOnFailedCall,
-  };
-
-  WebRtcSessionTest()
-      : callback_ids_(),
-        last_stream_id_(""),
-        last_was_video_(false),
-        last_description_ptr_(NULL),
-        last_candidates_(),
-        session_(NULL),
-        id_(),
-        receiving_(false),
-        allocator_(NULL),
-        channel_manager_(NULL),
-        worker_thread_(NULL),
-        signaling_thread_(NULL) {
-  }
-
-  ~WebRtcSessionTest() {
-    session_.reset();
-  }
-
-  void OnAddStream(const std::string& stream_id, bool video) {
-    callback_ids_.push_back(kOnAddStream);
-    last_stream_id_ = stream_id;
-    last_was_video_ = video;
-  }
-  void OnRemoveStream(const std::string& stream_id, bool video) {
-    callback_ids_.push_back(kOnRemoveStream);
-    last_stream_id_ = stream_id;
-    last_was_video_ = video;
-  }
-  void OnLocalDescription(
-      const cricket::SessionDescription* desc,
+  virtual void OnCandidatesReady(
       const std::vector<cricket::Candidate>& candidates) {
-    callback_ids_.push_back(kOnLocalDescription);
-    last_description_ptr_.reset(CopySessionDescription(desc));
-    CopyCandidates(candidates, &last_candidates_);
-  }
-  cricket::SessionDescription* GetLocalDescription(
-      std::vector<cricket::Candidate>* candidates) {
-    if (last_candidates_.empty()) {
-      return NULL;
+    for (cricket::Candidates::const_iterator iter = candidates.begin();
+         iter != candidates.end(); ++iter) {
+      candidates_.push_back(*iter);
+      LOG(LS_INFO) << iter->ToString();
     }
-    if (!last_description_ptr_.get()) {
-      return NULL;
-    }
-    CopyCandidates(last_candidates_, candidates);
-    return CopySessionDescription(last_description_ptr_.get());
   }
-
-  void OnFailedCall() {
-    callback_ids_.push_back(kOnFailedCall);
-  }
-
-  CallbackId PopOldestCallback() {
-    if (callback_ids_.empty()) {
-      return kNone;
-    }
-    const CallbackId return_value = callback_ids_.front();
-    callback_ids_.pop_front();
-    return return_value;
-  }
-
-  CallbackId PeekOldestCallback() {
-    if (callback_ids_.empty()) {
-      return kNone;
-    }
-    const CallbackId return_value = callback_ids_.front();
-    return return_value;
-  }
-
-  void Reset() {
-    callback_ids_.clear();
-    last_stream_id_ = "";
-    last_was_video_ = false;
-    last_description_ptr_.reset();
-    last_candidates_.clear();
-  }
-
-  bool WaitForCallback(CallbackId id, int timeout_ms) {
-    bool success = false;
-    for (int ms = 0; ms < timeout_ms; ms++) {
-      const CallbackId peek_id = PeekOldestCallback();
-      if (peek_id == id) {
-        PopOldestCallback();
-        success = true;
-        break;
-      } else if (peek_id != kNone) {
-        success = false;
-        break;
-      }
-      talk_base::Thread::Current()->ProcessMessages(1);
-    }
-    return success;
-  }
-
-  bool Init(bool receiving) {
-    if (signaling_thread_ != NULL)
-        return false;
-    signaling_thread_ = talk_base::Thread::Current();
-    receiving_ = receiving;
-
-    if (worker_thread_!= NULL)
-        return false;
-    worker_thread_ = talk_base::Thread::Current();
-
-    cricket::FakePortAllocator* fake_port_allocator =
-        new cricket::FakePortAllocator(worker_thread_, NULL);
-
-    allocator_.reset(static_cast<cricket::PortAllocator*>(fake_port_allocator));
-
-    channel_manager_.reset(new cricket::ChannelManager(worker_thread_));
-    if (!channel_manager_->Init())
-      return false;
-
-    talk_base::CreateRandomString(8, &id_);
-
-    session_.reset(new webrtc::WebRtcSession(
-        id_, receiving_ , allocator_.get(),
-        channel_manager_.get(),
-        signaling_thread_));
-    session_->SignalAddStream.connect(this, &WebRtcSessionTest::OnAddStream);
-    session_->SignalRemoveStream.connect(this,
-        &WebRtcSessionTest::OnRemoveStream);
-    session_->SignalLocalDescription.connect(this,
-        &WebRtcSessionTest::OnLocalDescription);
-    session_->SignalFailedCall.connect(this, &WebRtcSessionTest::OnFailedCall);
-    return true;
-  }
-
-  // All session APIs must be called from the signaling thread.
-  bool CallInitiate() {
-    return session_->Initiate();
-  }
-
-  bool CallConnect() {
-    if (!session_->Connect())
-      return false;
-    // This callback does not happen with FakeTransport!
-    if (!WaitForCallback(kOnLocalDescription, 1000)) {
-      return false;
-    }
-    return true;
-  }
-
-  bool CallOnRemoteDescription(
-      cricket::SessionDescription* description,
-      std::vector<cricket::Candidate> candidates) {
-    if (!session_->OnRemoteDescription(description, candidates)) {
-      return false;
-    }
-    if (!WaitForCallback(kOnAddStream, 1000)) {
-      return false;
-    }
-    return true;
-  }
-
-  bool CallOnInitiateMessage(
-      cricket::SessionDescription* description,
-      const std::vector<cricket::Candidate>& candidates) {
-    if (!session_->OnInitiateMessage(description, candidates)) {
-      return false;
-    }
-    if (!WaitForCallback(kOnAddStream, 1000)) {
-      return false;
-    }
-    return true;
-  }
-
-  bool CallCreateVoiceChannel(const std::string& stream_id) {
-    if (!session_->CreateVoiceChannel(stream_id)) {
-      return false;
-    }
-    return true;
-  }
-
-  bool CallCreateVideoChannel(const std::string& stream_id) {
-    if (!session_->CreateVideoChannel(stream_id)) {
-      return false;
-    }
-    return true;
-  }
-
-  bool CallRemoveStream(const std::string& stream_id) {
-    return session_->RemoveStream(stream_id);
-  }
-
-  void CallRemoveAllStreams() {
-    session_->RemoveAllStreams();
-  }
-
-  bool CallHasChannel(const std::string& label) {
-    return session_->HasStream(label);
-  }
-
-  bool CallHasChannel(bool video) {
-    return session_->HasChannel(video);
-  }
-
-  bool CallHasAudioChannel() {
-    return session_->HasAudioChannel();
-  }
-
-  bool CallHasVideoChannel() {
-    return session_->HasVideoChannel();
-  }
-
-  bool CallSetVideoRenderer(const std::string& stream_id,
-                            cricket::VideoRenderer* renderer) {
-    return session_->SetVideoRenderer(stream_id, renderer);
-  }
-
-  const std::vector<cricket::Candidate>& CallLocalCandidates() {
-    return session_->local_candidates();
-  }
-
- private:
-  std::list<CallbackId> callback_ids_;
-
-  std::string last_stream_id_;
-  bool last_was_video_;
-  talk_base::scoped_ptr<cricket::SessionDescription> last_description_ptr_;
-  std::vector<cricket::Candidate> last_candidates_;
-
-  talk_base::scoped_ptr<webrtc::WebRtcSession> session_;
-  std::string id_;
-  bool receiving_;
-
-  talk_base::scoped_ptr<cricket::PortAllocator> allocator_;
-
-  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
-
-  talk_base::Thread* worker_thread_;
-  talk_base::Thread* signaling_thread_;
+  std::vector<cricket::Candidate> candidates_;
 };
 
-bool CallbackReceived(WebRtcSessionTest* session, int timeout) {
-  EXPECT_EQ_WAIT(WebRtcSessionTest::kNone, session->PeekOldestCallback(),
-                 timeout);
-  const WebRtcSessionTest::CallbackId peek_id =
-      session->PeekOldestCallback();
-  return peek_id != WebRtcSessionTest::kNone;
-}
+class WebRtcSessionForTest : public webrtc::WebRtcSession {
+ public:
+  WebRtcSessionForTest(cricket::ChannelManager* cmgr,
+                       talk_base::Thread* signaling_thread,
+                       talk_base::Thread* worker_thread,
+                       cricket::PortAllocator* port_allocator)
+    : WebRtcSession(cmgr, signaling_thread, worker_thread, port_allocator) {
+  }
+  virtual ~WebRtcSessionForTest() {}
 
-TEST_F(WebRtcSessionTest, InitializationReceiveSanity) {
-  const bool kReceiving = true;
-  ASSERT_TRUE(Init(kReceiving));
-  ASSERT_TRUE(CallInitiate());
+  using webrtc::WebRtcSession::CreateOffer;
+  using webrtc::WebRtcSession::CreateAnswer;
+  using webrtc::WebRtcSession::SetLocalDescription;
+  using webrtc::WebRtcSession::SetRemoteDescription;
+  using webrtc::WebRtcSession::SetRemoteCandidates;
+};
 
-  // Should return false because no stream has been set up yet.
-  EXPECT_FALSE(CallConnect());
-  const bool kVideo = true;
-  EXPECT_FALSE(CallHasChannel(kVideo));
-  EXPECT_FALSE(CallHasChannel(!kVideo));
-
-  EXPECT_EQ(kNone, PopOldestCallback());
-}
-
-TEST_F(WebRtcSessionTest, AudioSendCallSetUp) {
-  const bool kReceiving = false;
-  ASSERT_TRUE(Init(kReceiving));
-
-  ASSERT_TRUE(CallInitiate());
-
-  ASSERT_TRUE(CallCreateVoiceChannel("Audio"));
-  ASSERT_TRUE(CallConnect());
-
-  std::vector<cricket::Candidate> candidates;
-  cricket::SessionDescription* local_session = GetLocalDescription(
-      &candidates);
-  ASSERT_FALSE(candidates.empty());
-  ASSERT_FALSE(local_session == NULL);
-  if (!CallOnRemoteDescription(local_session, candidates)) {
-      delete local_session;
-      FAIL();
+class WebRtcSessionTest : public testing::Test {
+ protected:
+  // TODO Investigate why ChannelManager crashes, if it's created
+  // after stun_server.
+  WebRtcSessionTest()
+    : media_engine(new cricket::FakeMediaEngine()),
+      device_manager(new cricket::FakeDeviceManager()),
+     channel_manager_(new cricket::ChannelManager(
+         media_engine, device_manager, talk_base::Thread::Current())),
+      desc_factory_(new cricket::MediaSessionDescriptionFactory(
+          channel_manager_.get())),
+      pss_(new talk_base::PhysicalSocketServer),
+      vss_(new talk_base::VirtualSocketServer(pss_.get())),
+      fss_(new talk_base::FirewallSocketServer(vss_.get())),
+      ss_scope_(fss_.get()),
+      stun_server_(talk_base::Thread::Current(), kStunAddr),
+      allocator_(&network_manager_, kStunAddr,
+                 SocketAddress(), SocketAddress(), SocketAddress()) {
+    EXPECT_TRUE(channel_manager_->Init());
   }
 
-  // All callbacks should be caught. Assert it.
-  ASSERT_FALSE(CallbackReceived(this, 1000));
-  ASSERT_TRUE(CallHasAudioChannel() &&
-              !CallHasVideoChannel());
-}
-
-TEST_F(WebRtcSessionTest, VideoSendCallSetUp) {
-  const bool kReceiving = false;
-  ASSERT_TRUE(Init(kReceiving));
-
-  ASSERT_TRUE(CallInitiate());
-
-  ASSERT_TRUE(CallCreateVideoChannel("Video"));
-  ASSERT_TRUE(CallConnect());
-
-  std::vector<cricket::Candidate> candidates;
-  cricket::SessionDescription* local_session = GetLocalDescription(
-      &candidates);
-  ASSERT_FALSE(candidates.empty());
-  ASSERT_FALSE(local_session == NULL);
-
-  if (!CallOnRemoteDescription(local_session, candidates)) {
-      delete local_session;
-      FAIL();
+  bool InitializeSession() {
+    return session_->Initialize();
   }
 
-  // All callbacks should be caught. Assert it.
-  ASSERT_FALSE(CallbackReceived(this, 1000));
-  ASSERT_TRUE(!CallHasAudioChannel() &&
-              CallHasVideoChannel());
+  void AddInterface(const SocketAddress& addr) {
+    network_manager_.AddInterface(addr);
+  }
+
+  void Init() {
+    ASSERT_TRUE(session_.get() == NULL);
+    session_.reset(new WebRtcSessionForTest(
+        channel_manager_.get(), talk_base::Thread::Current(),
+        talk_base::Thread::Current(), &allocator_));
+    session_->RegisterObserver(&observer_);
+
+    EXPECT_TRUE(session_->Initialize());
+
+    video_channel_ = media_engine->GetVideoChannel(0);
+    voice_channel_ = media_engine->GetVoiceChannel(0);
+  }
+
+  void PopulateFakeCandidates() {
+    const int num_of_channels = 4;
+    const char* const channel_names[num_of_channels] = {
+        "rtp", "rtcp", "video_rtp", "video_rtcp"
+    };
+
+    // max 4 transport channels;
+    candidates_.clear();
+    for (int i = 0; i < num_of_channels; ++i) {
+      cricket::Candidate candidate;
+      candidate.set_name(channel_names[i]);
+      candidates_.push_back(candidate);
+    }
+  }
+
+  // Create a session description based on options. Used for testing but don't
+  // test WebRtcSession.
+  cricket::SessionDescription* CreateTestOffer(
+      const cricket::MediaSessionOptions& options) {
+    desc_factory_->set_secure(cricket::SEC_REQUIRED);
+    return desc_factory_->CreateOffer(options, NULL);
+  }
+
+  // Create a session description based on options. Used for testing but don't
+  // test WebRtcSession.
+  cricket::SessionDescription* CreateTestAnswer(
+      const cricket::SessionDescription* offer,
+      const cricket::MediaSessionOptions& options) {
+    desc_factory_->set_secure(cricket::SEC_REQUIRED);
+    return desc_factory_->CreateAnswer(offer, options, NULL);
+  }
+
+  cricket::MediaSessionOptions OptionsWithStream1() {
+    cricket::MediaSessionOptions options;
+    options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack1, kStream1);
+    options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack1, kStream1);
+    return options;
+  }
+
+  cricket::MediaSessionOptions OptionsWithStream2() {
+    cricket::MediaSessionOptions options;
+    options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack2, kStream2);
+    options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack2, kStream2);
+    return options;
+  }
+
+  cricket::MediaSessionOptions OptionsWithStream1And2() {
+    cricket::MediaSessionOptions options;
+    options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack1, kStream1);
+    options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack1, kStream1);
+    options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack2, kStream2);
+    options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack2, kStream2);
+    return options;
+  }
+
+  cricket::MediaSessionOptions OptionsReceiveOnly() {
+    cricket::MediaSessionOptions options;
+    options.has_video = true;
+    return options;
+  }
+
+  bool ChannelsExist() {
+    return (session_->voice_channel() != NULL &&
+            session_->video_channel() != NULL);
+  }
+
+  void CheckTransportChannels() {
+    EXPECT_TRUE(session_->GetChannel(cricket::CN_AUDIO, "rtp") != NULL);
+    EXPECT_TRUE(session_->GetChannel(cricket::CN_AUDIO, "rtcp") != NULL);
+    EXPECT_TRUE(session_->GetChannel(cricket::CN_VIDEO, "video_rtp") != NULL);
+    EXPECT_TRUE(session_->GetChannel(cricket::CN_VIDEO, "video_rtcp") != NULL);
+  }
+
+  void VerifyCryptoParams(const cricket::SessionDescription* sdp,
+                          bool offer) {
+    ASSERT_TRUE(session_.get() != NULL);
+    const cricket::ContentInfo* content = cricket::GetFirstAudioContent(sdp);
+    ASSERT_TRUE(content != NULL);
+    const cricket::AudioContentDescription* audio_content =
+        static_cast<const cricket::AudioContentDescription*>(
+            content->description);
+    ASSERT_TRUE(audio_content != NULL);
+    if (offer) {
+      ASSERT_EQ(2U, audio_content->cryptos().size());
+      // key(40) + inline string
+      ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size());
+      ASSERT_EQ("AES_CM_128_HMAC_SHA1_32",
+                audio_content->cryptos()[0].cipher_suite);
+      ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+                audio_content->cryptos()[1].cipher_suite);
+      ASSERT_EQ(47U, audio_content->cryptos()[1].key_params.size());
+    } else {
+      ASSERT_EQ(1U, audio_content->cryptos().size());
+      // key(40) + inline string
+      ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size());
+      ASSERT_EQ("AES_CM_128_HMAC_SHA1_32",
+                audio_content->cryptos()[0].cipher_suite);
+    }
+
+    content = cricket::GetFirstVideoContent(sdp);
+    ASSERT_TRUE(content != NULL);
+    const cricket::VideoContentDescription* video_content =
+        static_cast<const cricket::VideoContentDescription*>(
+            content->description);
+    ASSERT_TRUE(video_content != NULL);
+    ASSERT_EQ(1U, video_content->cryptos().size());
+    ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+              video_content->cryptos()[0].cipher_suite);
+    ASSERT_EQ(47U, video_content->cryptos()[0].key_params.size());
+  }
+
+  void VerifyNoCryptoParams(const cricket::SessionDescription* sdp) {
+    const cricket::ContentInfo* content = cricket::GetFirstAudioContent(sdp);
+    ASSERT_TRUE(content != NULL);
+    const cricket::AudioContentDescription* audio_content =
+        static_cast<const cricket::AudioContentDescription*>(
+            content->description);
+    ASSERT_TRUE(audio_content != NULL);
+    ASSERT_EQ(0U, audio_content->cryptos().size());
+
+    content = cricket::GetFirstVideoContent(sdp);
+    ASSERT_TRUE(content != NULL);
+    const cricket::VideoContentDescription* video_content =
+        static_cast<const cricket::VideoContentDescription*>(
+            content->description);
+    ASSERT_TRUE(video_content != NULL);
+    ASSERT_EQ(0U, video_content->cryptos().size());
+  }
+
+  void VerifyAnswerFromNonCryptoOffer() {
+    // Create a SDP without Crypto.
+    desc_factory_->set_secure(cricket::SEC_DISABLED);
+    cricket::MediaSessionOptions options;
+    options.has_video = true;
+    cricket::SessionDescription* offer =
+        desc_factory_->CreateOffer(options, NULL);
+    ASSERT_TRUE(offer != NULL);
+    VerifyNoCryptoParams(offer);
+    const cricket::SessionDescription* answer =
+        session_->CreateAnswer(offer, options);
+    // Answer should be NULL as no crypto params in offer.
+    ASSERT_TRUE(answer == NULL);
+  }
+
+  void VerifyAnswerFromCryptoOffer() {
+    desc_factory_->set_secure(cricket::SEC_REQUIRED);
+    cricket::MediaSessionOptions options;
+    options.has_video = true;
+    cricket::SessionDescription* offer =
+        desc_factory_->CreateOffer(options, NULL);
+    ASSERT_TRUE(offer != NULL);
+    VerifyCryptoParams(offer, true);
+    const cricket::SessionDescription* answer =
+        session_->CreateAnswer(offer, options);
+    ASSERT_TRUE(answer != NULL);
+    VerifyCryptoParams(answer, false);
+  }
+
+  cricket::FakeMediaEngine* media_engine;
+  cricket::FakeDeviceManager* device_manager;
+  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+  talk_base::scoped_ptr<cricket::MediaSessionDescriptionFactory> desc_factory_;
+  talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
+  talk_base::scoped_ptr<talk_base::VirtualSocketServer> vss_;
+  talk_base::scoped_ptr<talk_base::FirewallSocketServer> fss_;
+  talk_base::SocketServerScope ss_scope_;
+  cricket::TestStunServer stun_server_;
+  talk_base::FakeNetworkManager network_manager_;
+  cricket::BasicPortAllocator allocator_;
+  talk_base::scoped_ptr<WebRtcSessionForTest> session_;
+  MockWebRtcSessionObserver observer_;
+  std::vector<cricket::Candidate> candidates_;
+  cricket::FakeVideoMediaChannel* video_channel_;
+  cricket::FakeVoiceMediaChannel* voice_channel_;
+};
+
+TEST_F(WebRtcSessionTest, TestInitialize) {
+  WebRtcSessionTest::Init();
+  EXPECT_TRUE(ChannelsExist());
+  CheckTransportChannels();
 }
 
-TEST_F(WebRtcSessionTest, AudioReceiveCallSetUp) {
-  const bool kReceiving = true;
-  const bool video = false;
-
-  ASSERT_TRUE(Init(kReceiving));
-
-  std::vector<cricket::Candidate> candidates;
-  cricket::SessionDescription* local_session =
-      GenerateFakeSession(video, &candidates);
-  ASSERT_FALSE(candidates.empty());
-  ASSERT_FALSE(local_session == NULL);
-  ASSERT_TRUE(CallInitiate());
-  if (!CallOnInitiateMessage(local_session, candidates)) {
-    delete local_session;
-    FAIL();
-  }
-  ASSERT_TRUE(CallConnect());
-  ASSERT_FALSE(CallbackReceived(this, 1000));
-
-  ASSERT_TRUE(CallHasAudioChannel() &&
-              !CallHasVideoChannel());
+TEST_F(WebRtcSessionTest, TestSessionCandidates) {
+  AddInterface(kClientAddr1);
+  WebRtcSessionTest::Init();
+  EXPECT_EQ_WAIT(8u, observer_.candidates_.size(), 3000);
 }
 
-TEST_F(WebRtcSessionTest, VideoReceiveCallSetUp) {
-  const bool kReceiving = true;
-  const bool video = true;
+TEST_F(WebRtcSessionTest, TestMultihomeCandidataes) {
+  AddInterface(kClientAddr1);
+  AddInterface(kClientAddr2);
+  WebRtcSessionTest::Init();
+  EXPECT_EQ_WAIT(16u, observer_.candidates_.size(), 3000);
+}
 
-  ASSERT_TRUE(Init(kReceiving));
+TEST_F(WebRtcSessionTest, TestStunError) {
+  AddInterface(kClientAddr1);
+  AddInterface(kClientAddr2);
+  fss_->AddRule(false, talk_base::FP_UDP, talk_base::FD_ANY, kClientAddr1);
+  WebRtcSessionTest::Init();
+  // Since kClientAddr1 is blocked, not expecting stun candidates for it.
+  EXPECT_EQ_WAIT(12u, observer_.candidates_.size(), 3000);
+}
 
-  std::vector<cricket::Candidate> candidates;
-  cricket::SessionDescription* local_session =
-      GenerateFakeSession(video, &candidates);
-  ASSERT_FALSE(candidates.empty());
-  ASSERT_FALSE(local_session == NULL);
-  ASSERT_TRUE(CallInitiate());
-  if (!CallOnInitiateMessage(local_session, candidates)) {
-    delete local_session;
-    FAIL();
-  }
-  ASSERT_TRUE(CallConnect());
-  ASSERT_FALSE(CallbackReceived(this, 1000));
-  ASSERT_TRUE(!CallHasAudioChannel() &&
-              CallHasVideoChannel());
+// Test creating offers and receive answers and make sure the
+// media engine creates the expected send and receive streams.
+TEST_F(WebRtcSessionTest, TestCreateOfferReceiveAnswer) {
+  WebRtcSessionTest::Init();
+  cricket::MediaSessionOptions options = OptionsWithStream1();
+  cricket::SessionDescription* offer = session_->CreateOffer(options);
+
+
+  cricket::MediaSessionOptions options2 = OptionsWithStream2();
+  cricket::SessionDescription* answer =  CreateTestAnswer(offer, options2);
+
+
+  session_->SetLocalDescription(offer, cricket::CA_OFFER);
+  session_->SetRemoteDescription(answer, cricket::CA_ANSWER);
+
+  ASSERT_EQ(1u, video_channel_->recv_streams().size());
+  cricket::StreamParams recv_video_stream =
+      video_channel_->recv_streams()[0];
+  EXPECT_TRUE(kVideoTrack2 == recv_video_stream.name);
+
+  ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+  cricket::StreamParams recv_audio_stream =
+      voice_channel_->recv_streams()[0];
+  EXPECT_TRUE(kAudioTrack2 == recv_audio_stream.name);
+
+  ASSERT_EQ(1u, video_channel_->send_streams().size());
+  EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].name);
+  ASSERT_EQ(1u, voice_channel_->send_streams().size());
+  EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].name);
+
+  // Create new offer without send streams.
+  offer = session_->CreateOffer(OptionsReceiveOnly());
+  // Test with same answer.
+  session_->SetLocalDescription(offer, cricket::CA_OFFER);
+  session_->SetRemoteDescription(answer, cricket::CA_ANSWER);
+
+  EXPECT_EQ(0u, video_channel_->send_streams().size());
+  EXPECT_EQ(0u, voice_channel_->send_streams().size());
+
+  // Make sure the receive streams have not changed.
+  ASSERT_EQ(1u, video_channel_->recv_streams().size());
+  EXPECT_EQ(recv_video_stream, video_channel_->recv_streams()[0]);
+  ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+  EXPECT_EQ(recv_audio_stream, voice_channel_->recv_streams()[0]);
+}
+
+// Test receiving offers and creating answers and make sure the
+// media engine creates the expected send and receive streams.
+TEST_F(WebRtcSessionTest, TestReceiveOfferCreateAnswer) {
+  WebRtcSessionTest::Init();
+  cricket::SessionDescription* offer = CreateTestOffer(OptionsWithStream2());
+
+  cricket::MediaSessionOptions answer_options = OptionsWithStream1();
+  cricket::SessionDescription* answer =
+        session_->CreateAnswer(offer, answer_options);
+  session_->SetRemoteDescription(offer, cricket::CA_OFFER);
+  session_->SetLocalDescription(answer, cricket::CA_ANSWER);
+
+  ASSERT_EQ(1u, video_channel_->recv_streams().size());
+  EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].name);
+
+  ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+  EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[0].name);
+
+  ASSERT_EQ(1u, video_channel_->send_streams().size());
+  EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].name);
+  ASSERT_EQ(1u, voice_channel_->send_streams().size());
+  EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].name);
+
+  offer = CreateTestOffer(OptionsWithStream1And2());
+
+  // Answer by turning off all send streams.
+  answer = session_->CreateAnswer(offer, OptionsReceiveOnly());
+  session_->SetRemoteDescription(offer, cricket::CA_OFFER);
+  session_->SetLocalDescription(answer, cricket::CA_ANSWER);
+
+  ASSERT_EQ(2u, video_channel_->recv_streams().size());
+  EXPECT_TRUE(kVideoTrack1 == video_channel_->recv_streams()[0].name);
+  EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[1].name);
+  ASSERT_EQ(2u, voice_channel_->recv_streams().size());
+  EXPECT_TRUE(kAudioTrack1 == voice_channel_->recv_streams()[0].name);
+  EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[1].name);
+
+  // Make we have no send streams.
+  EXPECT_EQ(0u, video_channel_->send_streams().size());
+  EXPECT_EQ(0u, voice_channel_->send_streams().size());
+}
+
+TEST_F(WebRtcSessionTest, TestDefaultSetSecurePolicy) {
+  WebRtcSessionTest::Init();
+  EXPECT_EQ(cricket::SEC_REQUIRED, session_->secure_policy());
+}
+
+TEST_F(WebRtcSessionTest, VerifyCryptoParamsInSDP) {
+  WebRtcSessionTest::Init();
+  VerifyCryptoParams(session_->CreateOffer(OptionsWithStream1()), true);
+}
+
+TEST_F(WebRtcSessionTest, VerifyNoCryptoParamsInSDP) {
+  WebRtcSessionTest::Init();
+  session_->set_secure_policy(cricket::SEC_DISABLED);
+  VerifyNoCryptoParams(session_->CreateOffer(OptionsWithStream1()));
+}
+
+TEST_F(WebRtcSessionTest, VerifyAnswerFromNonCryptoOffer) {
+  WebRtcSessionTest::Init();
+  VerifyAnswerFromNonCryptoOffer();
+}
+
+TEST_F(WebRtcSessionTest, VerifyAnswerFromCryptoOffer) {
+  WebRtcSessionTest::Init();
+  VerifyAnswerFromCryptoOffer();
 }
diff --git a/talk/app/webrtc/webrtcsessionobserver.h b/talk/app/webrtc/webrtcsessionobserver.h
new file mode 100644
index 0000000..c1653c9
--- /dev/null
+++ b/talk/app/webrtc/webrtcsessionobserver.h
@@ -0,0 +1,47 @@
+/*
+ * 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_APP_WEBRTC_WEBRTCSESSIONOBSERVER_H_
+#define TALK_APP_WEBRTC_WEBRTCSESSIONOBSERVER_H_
+
+#include <vector>
+
+#include "talk/p2p/base/candidate.h"
+
+namespace webrtc {
+
+class WebRtcSessionObserver {
+ public:
+  virtual void OnCandidatesReady(
+      const std::vector<cricket::Candidate>& candiddates) = 0;
+ protected:
+  virtual ~WebRtcSessionObserver() {}
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_WEBRTCSESSIONOBSERVER_H_
diff --git a/talk/app/webrtcv1/peerconnection.h b/talk/app/webrtcv1/peerconnection.h
new file mode 100644
index 0000000..c28508f
--- /dev/null
+++ b/talk/app/webrtcv1/peerconnection.h
@@ -0,0 +1,131 @@
+/*
+ * 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_PEERCONNECTION_H_
+#define TALK_APP_WEBRTC_PEERCONNECTION_H_
+
+// TODO - Add a factory class or some kind of PeerConnection manager
+// to support multiple PeerConnection object instantiation. This class will
+// create ChannelManager object and pass it to PeerConnection object. Otherwise
+// each PeerConnection object will have its own ChannelManager hence MediaEngine
+// and VoiceEngine/VideoEngine.
+
+#include <string>
+
+namespace cricket {
+class VideoRenderer;
+}
+
+namespace talk_base {
+class Thread;
+}
+
+namespace webrtc {
+
+class PeerConnectionObserver {
+ public:
+  // serialized signaling message
+  virtual void OnSignalingMessage(const std::string& msg) = 0;
+
+  // Triggered when a remote peer accepts a media connection.
+  virtual void OnAddStream(const std::string& stream_id, bool video) = 0;
+
+  // Triggered when a remote peer closes a media stream.
+  virtual void OnRemoveStream(const std::string& stream_id, bool video) = 0;
+
+ protected:
+  // Dtor protected as objects shouldn't be deleted via this interface.
+  virtual ~PeerConnectionObserver() {}
+};
+
+class PeerConnection {
+ public:
+  enum ReadyState {
+    NEW = 0,
+    NEGOTIATING,
+    ACTIVE,
+    CLOSED,
+  };
+
+  virtual ~PeerConnection() {}
+
+  // Register a listener
+  virtual void RegisterObserver(PeerConnectionObserver* observer) = 0;
+
+  // SignalingMessage in json format
+  virtual bool SignalingMessage(const std::string& msg) = 0;
+
+  // Asynchronously adds a local stream device to the peer
+  // connection.
+  virtual bool AddStream(const std::string& stream_id, bool video) = 0;
+
+  // Asynchronously removes a local stream device from the peer
+  // connection. The operation is complete when
+  // PeerConnectionObserver::OnRemoveStream is called.
+  virtual bool RemoveStream(const std::string& stream_id) = 0;
+
+  // Info the peerconnection that it is time to return the signaling
+  // information. The operation is complete when
+  // PeerConnectionObserver::OnSignalingMessage is called.
+  virtual bool Connect() = 0;
+
+  // Remove all the streams and tear down the session.
+  // After the Close() is called, the OnSignalingMessage will be invoked
+  // asynchronously. And before OnSignalingMessage is called,
+  // OnRemoveStream will be called for each stream that was active.
+  // TODO: Add an event such as onclose, or onreadystatechanged
+  // when the readystate reaches the closed state (no more streams in the
+  // peerconnection object.
+  virtual bool Close() = 0;
+
+  // Set the audio input & output devices based on the given device name.
+  // An empty device name means to use the default audio device.
+  virtual bool SetAudioDevice(const std::string& wave_in_device,
+                              const std::string& wave_out_device,
+                              int opts) = 0;
+
+  // Set the video renderer for the camera preview.
+  virtual bool SetLocalVideoRenderer(cricket::VideoRenderer* renderer) = 0;
+
+  // Set the video renderer for the specified stream.
+  virtual bool SetVideoRenderer(const std::string& stream_id,
+                                cricket::VideoRenderer* renderer) = 0;
+
+  // Set video capture device
+  // For Chromium the cam_device should use the capture session id.
+  // For standalone app, cam_device is the camera name. It will try to
+  // set the default capture device when cam_device is "".
+  virtual bool SetVideoCapture(const std::string& cam_device) = 0;
+
+  // Returns the state of the PeerConnection object.  See the ReadyState
+  // enum for valid values.
+  virtual ReadyState GetReadyState() = 0;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTION_H_
diff --git a/talk/app/webrtc/peerconnectionfactory.cc b/talk/app/webrtcv1/peerconnectionfactory.cc
similarity index 90%
rename from talk/app/webrtc/peerconnectionfactory.cc
rename to talk/app/webrtcv1/peerconnectionfactory.cc
index 1b40c14..7bf2f89 100644
--- a/talk/app/webrtc/peerconnectionfactory.cc
+++ b/talk/app/webrtcv1/peerconnectionfactory.cc
@@ -25,9 +25,9 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "talk/app/webrtc/peerconnectionfactory.h"
+#include "talk/app/webrtcv1/peerconnectionfactory.h"
 
-#include "talk/app/webrtc/peerconnectionproxy.h"
+#include "talk/app/webrtcv1/peerconnectionproxy.h"
 #include "talk/base/logging.h"
 #include "talk/p2p/client/basicportallocator.h"
 #include "talk/session/phone/channelmanager.h"
@@ -35,22 +35,18 @@
 namespace webrtc {
 
 PeerConnectionFactory::PeerConnectionFactory(
-    cricket::PortAllocator* port_allocator,
     cricket::MediaEngineInterface* media_engine,
     cricket::DeviceManagerInterface* 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)) {
 }
 
@@ -64,11 +60,12 @@
 }
 
 PeerConnection* PeerConnectionFactory::CreatePeerConnection(
+    cricket::PortAllocator* port_allocator,
     talk_base::Thread* signaling_thread) {
   PeerConnectionProxy* pc = NULL;
   if (initialized_) {
     pc =  new PeerConnectionProxy(
-        port_allocator_.get(), channel_manager_.get(), signaling_thread);
+        port_allocator, channel_manager_.get(), signaling_thread);
     if (!pc->Init()) {
       LOG(LERROR) << "Error in initializing PeerConnection";
       delete pc;
diff --git a/talk/app/webrtc/peerconnectionfactory.h b/talk/app/webrtcv1/peerconnectionfactory.h
similarity index 85%
rename from talk/app/webrtc/peerconnectionfactory.h
rename to talk/app/webrtcv1/peerconnectionfactory.h
index 7c65e05..ea509d6 100644
--- a/talk/app/webrtc/peerconnectionfactory.h
+++ b/talk/app/webrtcv1/peerconnectionfactory.h
@@ -51,21 +51,20 @@
 
 class PeerConnectionFactory {
  public:
-  PeerConnectionFactory(cricket::PortAllocator* port_allocator,
-                        cricket::MediaEngineInterface* media_engine,
+  PeerConnectionFactory(cricket::MediaEngineInterface* media_engine,
                         cricket::DeviceManagerInterface* device_manager,
                         talk_base::Thread* worker_thread);
-  PeerConnectionFactory(cricket::PortAllocator* port_allocator,
-                        talk_base::Thread* worker_thread);
+  PeerConnectionFactory(talk_base::Thread* worker_thread);
 
   virtual ~PeerConnectionFactory();
   bool Initialize();
 
-  PeerConnection* CreatePeerConnection(talk_base::Thread* signaling_thread);
+  PeerConnection* CreatePeerConnection(
+      cricket::PortAllocator* port_allocator,
+      talk_base::Thread* signaling_thread);
 
  private:
   bool initialized_;
-  talk_base::scoped_ptr<cricket::PortAllocator> port_allocator_;
   talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
 };
 
diff --git a/talk/app/webrtcv1/peerconnectionimpl.cc b/talk/app/webrtcv1/peerconnectionimpl.cc
new file mode 100644
index 0000000..be67859
--- /dev/null
+++ b/talk/app/webrtcv1/peerconnectionimpl.cc
@@ -0,0 +1,225 @@
+/*
+ * 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/webrtcv1/peerconnectionimpl.h"
+
+#include "talk/app/webrtcv1/webrtcjson.h"
+#include "talk/app/webrtcv1/webrtcsession.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/client/basicportallocator.h"
+
+namespace webrtc {
+
+
+PeerConnectionImpl::PeerConnectionImpl(
+    cricket::PortAllocator* port_allocator,
+    cricket::ChannelManager* channel_manager,
+    talk_base::Thread* signaling_thread)
+  : port_allocator_(port_allocator),
+    channel_manager_(channel_manager),
+    signaling_thread_(signaling_thread),
+    event_callback_(NULL),
+    session_(NULL) {
+}
+
+PeerConnectionImpl::~PeerConnectionImpl() {
+}
+
+bool PeerConnectionImpl::Init() {
+  std::string sid;
+  talk_base::CreateRandomString(8, &sid);
+  const bool incoming = false;
+  // default outgoing direction
+  session_.reset(CreateMediaSession(sid, incoming));
+  if (session_.get() == NULL) {
+    ASSERT(false && "failed to initialize a session");
+    return false;
+  }
+  return true;
+}
+
+void PeerConnectionImpl::RegisterObserver(PeerConnectionObserver* observer) {
+  // This assert is to catch cases where two observer pointers are registered.
+  // We only support one and if another is to be used, the current one must be
+  // cleared first.
+  ASSERT(observer == NULL || event_callback_ == NULL);
+  event_callback_ = observer;
+}
+
+bool PeerConnectionImpl::SignalingMessage(
+    const std::string& signaling_message) {
+  // Deserialize signaling message
+  cricket::SessionDescription* incoming_sdp = NULL;
+  std::vector<cricket::Candidate> candidates;
+  if (!ParseJsonSignalingMessage(signaling_message,
+                                 &incoming_sdp, &candidates)) {
+    return false;
+  }
+
+  bool ret = false;
+  if (GetReadyState() == NEW) {
+    // set direction to incoming, as message received first
+    session_->set_incoming(true);
+    ret = session_->OnInitiateMessage(incoming_sdp, candidates);
+  } else {
+    ret = session_->OnRemoteDescription(incoming_sdp, candidates);
+  }
+  return ret;
+}
+
+WebRtcSession* PeerConnectionImpl::CreateMediaSession(
+    const std::string& id, bool incoming) {
+  ASSERT(port_allocator_ != NULL);
+  WebRtcSession* session = new WebRtcSession(id, incoming,
+      port_allocator_, channel_manager_, signaling_thread_);
+
+  if (session->Initiate()) {
+    session->SignalAddStream.connect(
+        this,
+        &PeerConnectionImpl::OnAddStream);
+    session->SignalRemoveStream.connect(
+        this,
+        &PeerConnectionImpl::OnRemoveStream);
+    session->SignalLocalDescription.connect(
+        this,
+        &PeerConnectionImpl::OnLocalDescription);
+    session->SignalFailedCall.connect(
+        this,
+        &PeerConnectionImpl::OnFailedCall);
+  } else {
+    delete session;
+    session = NULL;
+  }
+  return session;
+}
+
+bool PeerConnectionImpl::AddStream(const std::string& stream_id, bool video) {
+  bool ret = false;
+  if (session_->HasStream(stream_id)) {
+    ASSERT(false && "A stream with this name already exists");
+  } else {
+    if (!video) {
+      ret = !session_->HasAudioChannel() &&
+            session_->CreateVoiceChannel(stream_id);
+    } else {
+      ret = !session_->HasVideoChannel() &&
+            session_->CreateVideoChannel(stream_id);
+    }
+  }
+  return ret;
+}
+
+bool PeerConnectionImpl::RemoveStream(const std::string& stream_id) {
+  return session_->RemoveStream(stream_id);
+}
+
+void PeerConnectionImpl::OnLocalDescription(
+    const cricket::SessionDescription* desc,
+    const std::vector<cricket::Candidate>& candidates) {
+  if (!desc) {
+    LOG(WARNING) << "no local SDP ";
+    return;
+  }
+
+  std::string message;
+  if (GetJsonSignalingMessage(desc, candidates, &message)) {
+    if (event_callback_) {
+      event_callback_->OnSignalingMessage(message);
+    }
+  }
+}
+
+void PeerConnectionImpl::OnFailedCall() {
+  // TODO: implement.
+}
+
+bool PeerConnectionImpl::SetAudioDevice(const std::string& wave_in_device,
+                                        const std::string& wave_out_device,
+                                        int opts) {
+  return channel_manager_->SetAudioOptions(wave_in_device,
+                                           wave_out_device,
+                                           opts);
+}
+
+bool PeerConnectionImpl::SetLocalVideoRenderer(
+    cricket::VideoRenderer* renderer) {
+  return channel_manager_->SetLocalRenderer(renderer);
+}
+
+bool PeerConnectionImpl::SetVideoRenderer(const std::string& stream_id,
+                                          cricket::VideoRenderer* renderer) {
+  return session_->SetVideoRenderer(stream_id, renderer);
+}
+
+bool PeerConnectionImpl::SetVideoCapture(const std::string& cam_device) {
+  return channel_manager_->SetVideoOptions(cam_device);
+}
+
+bool PeerConnectionImpl::Connect() {
+  return session_->Connect();
+}
+
+// TODO - Close is not used anymore, should be removed.
+bool PeerConnectionImpl::Close() {
+  session_->RemoveAllStreams();
+  return true;
+}
+
+void PeerConnectionImpl::OnAddStream(const std::string& stream_id,
+                                     bool video) {
+  if (event_callback_) {
+    event_callback_->OnAddStream(stream_id, video);
+  }
+}
+
+void PeerConnectionImpl::OnRemoveStream(const std::string& stream_id,
+                                        bool video) {
+  if (event_callback_) {
+    event_callback_->OnRemoveStream(stream_id, video);
+  }
+}
+
+PeerConnectionImpl::ReadyState PeerConnectionImpl::GetReadyState() {
+  ReadyState ready_state;
+  cricket::BaseSession::State state = session_->state();
+  if (state == cricket::BaseSession::STATE_INIT) {
+    ready_state = NEW;
+  } else if (state == cricket::BaseSession::STATE_INPROGRESS) {
+    ready_state = ACTIVE;
+  } else if (state == cricket::BaseSession::STATE_DEINIT) {
+    ready_state = CLOSED;
+  } else {
+    ready_state = NEGOTIATING;
+  }
+  return ready_state;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionproxy.h b/talk/app/webrtcv1/peerconnectionimpl.h
similarity index 63%
copy from talk/app/webrtc/peerconnectionproxy.h
copy to talk/app/webrtcv1/peerconnectionimpl.h
index e83b4ec..8cb2414 100644
--- a/talk/app/webrtc/peerconnectionproxy.h
+++ b/talk/app/webrtcv1/peerconnectionimpl.h
@@ -25,33 +25,36 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef TALK_APP_WEBRTC_PEERCONNECTIONPROXY_H_
-#define TALK_APP_WEBRTC_PEERCONNECTIONPROXY_H_
+#ifndef TALK_APP_WEBRTC_PEERCONNECTIONIMPL_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONIMPL_H_
 
 #include <string>
+#include <vector>
 
-#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtcv1/peerconnection.h"
+#include "talk/base/sigslot.h"
 #include "talk/base/scoped_ptr.h"
 #include "talk/base/thread.h"
+#include "talk/session/phone/channelmanager.h"
 
 namespace cricket {
 class ChannelManager;
 class PortAllocator;
+class SessionDescription;
 }
 
 namespace webrtc {
+class WebRtcSession;
 
-class PeerConnectionImpl;
-
-class PeerConnectionProxy : public PeerConnection,
-                            public talk_base::MessageHandler {
+class PeerConnectionImpl : public PeerConnection,
+                           public sigslot::has_slots<> {
  public:
-  PeerConnectionProxy(cricket::PortAllocator* port_allocator,
-                      cricket::ChannelManager* channel_manager,
-                      talk_base::Thread* signaling_thread);
-  virtual ~PeerConnectionProxy();
+  PeerConnectionImpl(cricket::PortAllocator* port_allocator,
+                     cricket::ChannelManager* channel_manager,
+                     talk_base::Thread* signaling_thread);
+  virtual ~PeerConnectionImpl();
 
-  // PeerConnection interface implementation.
+  // PeerConnection interfaces
   virtual void RegisterObserver(PeerConnectionObserver* observer);
   virtual bool SignalingMessage(const std::string& msg);
   virtual bool AddStream(const std::string& stream_id, bool video);
@@ -66,16 +69,29 @@
   virtual bool SetVideoCapture(const std::string& cam_device);
   virtual ReadyState GetReadyState();
 
- private:
+  cricket::ChannelManager* channel_manager() {
+    return channel_manager_;
+  }
+
+  // Callbacks from PeerConnectionImplCallbacks
+  void OnAddStream(const std::string& stream_id, bool video);
+  void OnRemoveStream(const std::string& stream_id, bool video);
+  void OnLocalDescription(
+      const cricket::SessionDescription* desc,
+      const std::vector<cricket::Candidate>& candidates);
+  void OnFailedCall();
   bool Init();
-  bool Send(uint32 id, talk_base::MessageData* data);
-  virtual void OnMessage(talk_base::Message* message);
 
-  talk_base::scoped_ptr<PeerConnectionImpl> peerconnection_impl_;
+ private:
+  WebRtcSession* CreateMediaSession(const std::string& id, bool incoming);
+
+  cricket::PortAllocator* port_allocator_;
+  cricket::ChannelManager* channel_manager_;
   talk_base::Thread* signaling_thread_;
-
-  friend class PeerConnectionFactory;
+  PeerConnectionObserver* event_callback_;
+  talk_base::scoped_ptr<WebRtcSession> session_;
 };
-}
 
-#endif  // TALK_APP_WEBRTC_PEERCONNECTIONPROXY_H_
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTIONIMPL_H_
diff --git a/talk/app/webrtc/peerconnectionproxy.cc b/talk/app/webrtcv1/peerconnectionproxy.cc
similarity index 98%
rename from talk/app/webrtc/peerconnectionproxy.cc
rename to talk/app/webrtcv1/peerconnectionproxy.cc
index fca3ad4..3994b00 100644
--- a/talk/app/webrtc/peerconnectionproxy.cc
+++ b/talk/app/webrtcv1/peerconnectionproxy.cc
@@ -25,9 +25,9 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "talk/app/webrtc/peerconnectionproxy.h"
+#include "talk/app/webrtcv1/peerconnectionproxy.h"
 
-#include "talk/app/webrtc/peerconnectionimpl.h"
+#include "talk/app/webrtcv1/peerconnectionimpl.h"
 #include "talk/base/logging.h"
 
 namespace webrtc {
diff --git a/talk/app/webrtc/peerconnectionproxy.h b/talk/app/webrtcv1/peerconnectionproxy.h
similarity index 98%
rename from talk/app/webrtc/peerconnectionproxy.h
rename to talk/app/webrtcv1/peerconnectionproxy.h
index e83b4ec..8d7fc26 100644
--- a/talk/app/webrtc/peerconnectionproxy.h
+++ b/talk/app/webrtcv1/peerconnectionproxy.h
@@ -30,7 +30,7 @@
 
 #include <string>
 
-#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtcv1/peerconnection.h"
 #include "talk/base/scoped_ptr.h"
 #include "talk/base/thread.h"
 
diff --git a/talk/app/webrtcv1/webrtcjson.cc b/talk/app/webrtcv1/webrtcjson.cc
new file mode 100644
index 0000000..644d76b
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcjson.cc
@@ -0,0 +1,530 @@
+/*
+ * 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/webrtcv1/webrtcjson.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "json/json.h"
+#else
+#include "third_party/jsoncpp/json.h"
+#endif
+
+// TODO: Remove webrtcsession.h once we can get size from signaling.
+// webrtcsession.h is for kDefaultVideoCodecWidth and kDefaultVideoCodecHeight.
+#include "talk/app/webrtcv1/webrtcsession.h"
+#include "talk/base/json.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/mediasessionclient.h"
+
+namespace webrtc {
+static const int kIceComponent = 1;
+static const int kIceFoundation = 1;
+
+static std::vector<Json::Value> ReadValues(const Json::Value& value,
+                                           const std::string& key);
+
+static bool BuildMediaMessage(
+    const cricket::ContentInfo& content_info,
+    const std::vector<cricket::Candidate>& candidates,
+    bool video,
+    Json::Value* value);
+
+static bool BuildRtpMapParams(
+    const cricket::ContentInfo& audio_offer,
+    bool video,
+    std::vector<Json::Value>* rtpmap);
+
+static void BuildCrypto(const cricket::ContentInfo& content_info,
+                        bool video,
+                        std::vector<Json::Value>* cryptos);
+
+
+static bool BuildAttributes(const std::vector<cricket::Candidate>& candidates,
+                            bool video,
+                            std::vector<Json::Value>* jcandidates);
+
+static std::string Serialize(const Json::Value& value);
+static bool Deserialize(const std::string& message, Json::Value* value);
+
+static bool ParseRtcpMux(const Json::Value& value);
+static bool ParseAudioCodec(const Json::Value& value,
+                            cricket::AudioContentDescription* content);
+static bool ParseVideoCodec(const Json::Value& value,
+                            cricket::VideoContentDescription* content);
+static bool ParseCrypto(const Json::Value& content,
+                        cricket::MediaContentDescription* desc);
+static bool ParseIceCandidates(const Json::Value& value,
+                               std::vector<cricket::Candidate>* candidates);
+
+static Json::Value ReadValue(const Json::Value& value, const std::string& key);
+static std::string ReadString(const Json::Value& value, const std::string& key);
+static uint32 ReadUInt(const Json::Value& value, const std::string& key);
+
+static void Append(Json::Value* object, const std::string& key, bool value);
+static void Append(Json::Value* object, const std::string& key, int value);
+static void Append(Json::Value* object, const std::string& key,
+                   const std::string& value);
+static void Append(Json::Value* object, const std::string& key, uint32 value);
+static void Append(Json::Value* object, const std::string& key,
+                   const Json::Value& value);
+static void Append(Json::Value* object,
+                   const std::string& key,
+                   const std::vector<Json::Value>& values);
+
+bool GetJsonSignalingMessage(
+    const cricket::SessionDescription* sdp,
+    const std::vector<cricket::Candidate>& candidates,
+    std::string* signaling_message) {
+  const cricket::ContentInfo* audio_content = GetFirstAudioContent(sdp);
+  const cricket::ContentInfo* video_content = GetFirstVideoContent(sdp);
+
+  std::vector<Json::Value> media;
+  if (audio_content) {
+    Json::Value value;
+    BuildMediaMessage(*audio_content, candidates, false, &value);
+    media.push_back(value);
+  }
+
+  if (video_content) {
+    Json::Value value;
+    BuildMediaMessage(*video_content, candidates, true, &value);
+    media.push_back(value);
+  }
+
+  Json::Value signal;
+  Append(&signal, "media", media);
+
+  // Now serialize.
+  *signaling_message = Serialize(signal);
+
+  return true;
+}
+
+bool BuildMediaMessage(
+    const cricket::ContentInfo& content_info,
+    const std::vector<cricket::Candidate>& candidates,
+    bool video,
+    Json::Value* params) {
+  if (video) {
+    Append(params, "label", 2);  // always video 2
+  } else {
+    Append(params, "label", 1);  // always audio 1
+  }
+
+  const cricket::MediaContentDescription* media_info =
+  static_cast<const cricket::MediaContentDescription*> (
+      content_info.description);
+  if (media_info->rtcp_mux()) {
+    Append(params, "rtcp_mux", true);
+  }
+
+  // rtpmap
+  std::vector<Json::Value> rtpmap;
+  if (!BuildRtpMapParams(content_info, video, &rtpmap)) {
+    return false;
+  }
+  Append(params, "rtpmap", rtpmap);
+
+  // crypto
+  std::vector<Json::Value> crypto;
+  BuildCrypto(content_info, video, &crypto);
+  Append(params, "crypto", crypto);
+
+  // Candidates
+  Json::Value attributes;
+  std::vector<Json::Value> jcandidates;
+  if (!BuildAttributes(candidates, video, &jcandidates)) {
+    return false;
+  }
+  Append(&attributes, "candidate", jcandidates);
+
+  Append(params, "attributes", attributes);
+  return true;
+}
+
+bool BuildRtpMapParams(const cricket::ContentInfo& content_info,
+                       bool video,
+                       std::vector<Json::Value>* rtpmap) {
+  if (!video) {
+    const cricket::AudioContentDescription* audio_offer =
+        static_cast<const cricket::AudioContentDescription*>(
+            content_info.description);
+
+    std::vector<cricket::AudioCodec>::const_iterator iter =
+        audio_offer->codecs().begin();
+    std::vector<cricket::AudioCodec>::const_iterator iter_end =
+        audio_offer->codecs().end();
+    for (; iter != iter_end; ++iter) {
+      Json::Value codec;
+      std::string codec_str(std::string("audio/").append(iter->name));
+      // adding clockrate
+      Append(&codec, "clockrate", iter->clockrate);
+      Append(&codec, "codec", codec_str);
+      Json::Value codec_id;
+      Append(&codec_id, talk_base::ToString(iter->id), codec);
+      rtpmap->push_back(codec_id);
+    }
+  } else {
+    const cricket::VideoContentDescription* video_offer =
+        static_cast<const cricket::VideoContentDescription*>(
+            content_info.description);
+
+    std::vector<cricket::VideoCodec>::const_iterator iter =
+        video_offer->codecs().begin();
+    std::vector<cricket::VideoCodec>::const_iterator iter_end =
+        video_offer->codecs().end();
+    for (; iter != iter_end; ++iter) {
+      Json::Value codec;
+      std::string codec_str(std::string("video/").append(iter->name));
+      Append(&codec, "codec", codec_str);
+      Json::Value codec_id;
+      Append(&codec_id, talk_base::ToString(iter->id), codec);
+      rtpmap->push_back(codec_id);
+    }
+  }
+  return true;
+}
+
+void BuildCrypto(const cricket::ContentInfo& content_info,
+                 bool video,
+                 std::vector<Json::Value>* cryptos) {
+  const cricket::MediaContentDescription* content_desc =
+      static_cast<const cricket::MediaContentDescription*>(
+          content_info.description);
+  std::vector<cricket::CryptoParams>::const_iterator iter =
+      content_desc->cryptos().begin();
+  std::vector<cricket::CryptoParams>::const_iterator iter_end =
+      content_desc->cryptos().end();
+  for (; iter != iter_end; ++iter) {
+    Json::Value crypto;
+    Append(&crypto, "cipher_suite", iter->cipher_suite);
+    Append(&crypto, "key_params", iter->key_params);
+    cryptos->push_back(crypto);
+  }
+}
+
+bool BuildAttributes(const std::vector<cricket::Candidate>& candidates,
+                     bool video,
+                     std::vector<Json::Value>* jcandidates) {
+  std::vector<cricket::Candidate>::const_iterator iter =
+      candidates.begin();
+  std::vector<cricket::Candidate>::const_iterator iter_end =
+      candidates.end();
+  for (; iter != iter_end; ++iter) {
+    if ((video && (!iter->name().compare("video_rtcp") ||
+                  (!iter->name().compare("video_rtp")))) ||
+        (!video && (!iter->name().compare("rtp") ||
+                   (!iter->name().compare("rtcp"))))) {
+      Json::Value candidate;
+      Append(&candidate, "component", kIceComponent);
+      Append(&candidate, "foundation", kIceFoundation);
+      Append(&candidate, "generation", iter->generation());
+      Append(&candidate, "proto", iter->protocol());
+      Append(&candidate, "priority", iter->preference_str());
+      Append(&candidate, "ip", iter->address().IPAsString());
+      Append(&candidate, "port", iter->address().PortAsString());
+      Append(&candidate, "type", iter->type());
+      Append(&candidate, "name", iter->name());
+      Append(&candidate, "network_name", iter->network_name());
+      Append(&candidate, "username", iter->username());
+      Append(&candidate, "password", iter->password());
+      jcandidates->push_back(candidate);
+    }
+  }
+  return true;
+}
+
+std::string Serialize(const Json::Value& value) {
+  Json::StyledWriter writer;
+  return writer.write(value);
+}
+
+bool Deserialize(const std::string& message, Json::Value* value) {
+  Json::Reader reader;
+  return reader.parse(message, *value);
+}
+
+bool ParseJsonSignalingMessage(const std::string& signaling_message,
+                               cricket::SessionDescription** sdp,
+                               std::vector<cricket::Candidate>* candidates) {
+  ASSERT(!(*sdp));  // expect this to be NULL
+  // first deserialize message
+  Json::Value value;
+  if (!Deserialize(signaling_message, &value)) {
+    return false;
+  }
+
+  // get media objects
+  std::vector<Json::Value> mlines = ReadValues(value, "media");
+  if (mlines.empty()) {
+    // no m-lines found
+    return false;
+  }
+
+  *sdp = new cricket::SessionDescription();
+
+  // get codec information
+  for (size_t i = 0; i < mlines.size(); ++i) {
+    if (mlines[i]["label"].asInt() == 1) {
+      cricket::AudioContentDescription* audio_content =
+          new cricket::AudioContentDescription();
+      ParseAudioCodec(mlines[i], audio_content);
+      audio_content->set_rtcp_mux(ParseRtcpMux(mlines[i]));
+      audio_content->SortCodecs();
+      (*sdp)->AddContent(cricket::CN_AUDIO,
+                         cricket::NS_JINGLE_RTP, audio_content);
+      // crypto
+      if (!ParseCrypto(mlines[i], audio_content))
+        return false;
+      ParseIceCandidates(mlines[i], candidates);
+    } else {
+      cricket::VideoContentDescription* video_content =
+          new cricket::VideoContentDescription();
+      ParseVideoCodec(mlines[i], video_content);
+
+      video_content->set_rtcp_mux(ParseRtcpMux(mlines[i]));
+      video_content->SortCodecs();
+      (*sdp)->AddContent(cricket::CN_VIDEO,
+                         cricket::NS_JINGLE_RTP, video_content);
+      if (!ParseCrypto(mlines[i], video_content))
+        return false;
+      ParseIceCandidates(mlines[i], candidates);
+    }
+  }
+  return true;
+}
+
+bool ParseRtcpMux(const Json::Value& value) {
+  Json::Value rtcp_mux(ReadValue(value, "rtcp_mux"));
+  if (!rtcp_mux.empty()) {
+    if (rtcp_mux.asBool()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool ParseAudioCodec(const Json::Value& value,
+                     cricket::AudioContentDescription* content) {
+  std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap"));
+  if (rtpmap.empty())
+    return false;
+
+  std::vector<Json::Value>::const_iterator iter =
+      rtpmap.begin();
+  std::vector<Json::Value>::const_iterator iter_end =
+      rtpmap.end();
+  for (; iter != iter_end; ++iter) {
+    cricket::AudioCodec codec;
+    std::string pltype(iter->begin().memberName());
+    talk_base::FromString(pltype, &codec.id);
+    Json::Value codec_info((*iter)[pltype]);
+    std::string codec_name(ReadString(codec_info, "codec"));
+    std::vector<std::string> tokens;
+    talk_base::split(codec_name, '/', &tokens);
+    codec.name = tokens[1];
+    codec.clockrate = ReadUInt(codec_info, "clockrate");
+    content->AddCodec(codec);
+  }
+
+  return true;
+}
+
+bool ParseVideoCodec(const Json::Value& value,
+                     cricket::VideoContentDescription* content) {
+  std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap"));
+  if (rtpmap.empty())
+    return false;
+
+  std::vector<Json::Value>::const_iterator iter =
+      rtpmap.begin();
+  std::vector<Json::Value>::const_iterator iter_end =
+      rtpmap.end();
+  for (; iter != iter_end; ++iter) {
+    cricket::VideoCodec codec;
+    std::string pltype(iter->begin().memberName());
+    talk_base::FromString(pltype, &codec.id);
+    Json::Value codec_info((*iter)[pltype]);
+    std::vector<std::string> tokens;
+    talk_base::split(codec_info["codec"].asString(), '/', &tokens);
+    codec.name = tokens[1];
+    // TODO: Remove once we can get size from signaling message.
+    codec.width = WebRtcSession::kDefaultVideoCodecWidth;
+    codec.height = WebRtcSession::kDefaultVideoCodecHeight;
+    content->AddCodec(codec);
+  }
+  return true;
+}
+
+bool ParseIceCandidates(const Json::Value& value,
+                        std::vector<cricket::Candidate>* candidates) {
+  Json::Value attributes(ReadValue(value, "attributes"));
+  std::string ice_pwd(ReadString(attributes, "ice-pwd"));
+  std::string ice_ufrag(ReadString(attributes, "ice-ufrag"));
+
+  std::vector<Json::Value> jcandidates(ReadValues(attributes, "candidate"));
+
+  std::vector<Json::Value>::const_iterator iter =
+      jcandidates.begin();
+  std::vector<Json::Value>::const_iterator iter_end =
+      jcandidates.end();
+  for (; iter != iter_end; ++iter) {
+    cricket::Candidate cand;
+
+    unsigned int generation;
+    if (!GetUIntFromJsonObject(*iter, "generation", &generation))
+      return false;
+    cand.set_generation_str(talk_base::ToString(generation));
+
+    std::string proto;
+    if (!GetStringFromJsonObject(*iter, "proto", &proto))
+      return false;
+    cand.set_protocol(proto);
+
+    std::string priority;
+    if (!GetStringFromJsonObject(*iter, "priority", &priority))
+      return false;
+    cand.set_preference_str(priority);
+
+    std::string str;
+    talk_base::SocketAddress addr;
+    if (!GetStringFromJsonObject(*iter, "ip", &str))
+      return false;
+    addr.SetIP(str);
+    if (!GetStringFromJsonObject(*iter, "port", &str))
+      return false;
+    int port;
+    if (!talk_base::FromString(str, &port))
+      return false;
+    addr.SetPort(port);
+    cand.set_address(addr);
+
+    if (!GetStringFromJsonObject(*iter, "type", &str))
+      return false;
+    cand.set_type(str);
+
+    if (!GetStringFromJsonObject(*iter, "name", &str))
+      return false;
+    cand.set_name(str);
+
+    if (!GetStringFromJsonObject(*iter, "network_name", &str))
+      return false;
+    cand.set_network_name(str);
+
+    if (!GetStringFromJsonObject(*iter, "username", &str))
+      return false;
+    cand.set_username(str);
+
+    if (!GetStringFromJsonObject(*iter, "password", &str))
+      return false;
+    cand.set_password(str);
+
+    candidates->push_back(cand);
+  }
+  return true;
+}
+
+bool ParseCrypto(const Json::Value& content,
+                 cricket::MediaContentDescription* desc) {
+  std::vector<Json::Value> jcryptos(ReadValues(content, "crypto"));
+  std::vector<Json::Value>::const_iterator iter =
+      jcryptos.begin();
+  std::vector<Json::Value>::const_iterator iter_end =
+      jcryptos.end();
+  for (; iter != iter_end; ++iter) {
+    cricket::CryptoParams crypto;
+
+    std::string cipher_suite;
+    if (!GetStringFromJsonObject(*iter, "cipher_suite", &cipher_suite))
+      return false;
+    crypto.cipher_suite = cipher_suite;
+
+    std::string key_params;
+    if (!GetStringFromJsonObject(*iter, "key_params", &key_params))
+      return false;
+    crypto.key_params= key_params;
+
+    desc->AddCrypto(crypto);
+  }
+  return true;
+}
+
+std::vector<Json::Value> ReadValues(
+    const Json::Value& value, const std::string& key) {
+  std::vector<Json::Value> objects;
+  for (Json::Value::ArrayIndex i = 0; i < value[key].size(); ++i) {
+    objects.push_back(value[key][i]);
+  }
+  return objects;
+}
+
+Json::Value ReadValue(const Json::Value& value, const std::string& key) {
+  return value[key];
+}
+
+std::string ReadString(const Json::Value& value, const std::string& key) {
+  return value[key].asString();
+}
+
+uint32 ReadUInt(const Json::Value& value, const std::string& key) {
+  return value[key].asUInt();
+}
+
+void Append(Json::Value* object, const std::string& key, bool value) {
+  (*object)[key] = Json::Value(value);
+}
+
+void Append(Json::Value* object, const std::string& key, int value) {
+  (*object)[key] = Json::Value(value);
+}
+
+void Append(Json::Value* object, const std::string& key,
+            const std::string& value) {
+  (*object)[key] = Json::Value(value);
+}
+
+void Append(Json::Value* object, const std::string& key, uint32 value) {
+  (*object)[key] = Json::Value(value);
+}
+
+void Append(Json::Value* object, const std::string& key,
+            const Json::Value& value) {
+  (*object)[key] = value;
+}
+
+void Append(Json::Value* object,
+            const std::string & key,
+            const std::vector<Json::Value>& values) {
+  for (std::vector<Json::Value>::const_iterator iter = values.begin();
+      iter != values.end(); ++iter) {
+    (*object)[key].append(*iter);
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtcv1/webrtcjson.h b/talk/app/webrtcv1/webrtcjson.h
new file mode 100644
index 0000000..d923f83
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcjson.h
@@ -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.
+ */
+
+#ifndef TALK_APP_WEBRTC_WEBRTCJSON_H_
+#define TALK_APP_WEBRTC_WEBRTCJSON_H_
+
+#include <string>
+
+#include "talk/p2p/base/candidate.h"
+#include "talk/session/phone/codec.h"
+
+namespace Json {
+class Value;
+}
+
+namespace cricket {
+class AudioContentDescription;
+class VideoContentDescription;
+struct ContentInfo;
+class SessionDescription;
+}
+
+namespace webrtc {
+
+bool GetJsonSignalingMessage(
+    const cricket::SessionDescription* sdp,
+    const std::vector<cricket::Candidate>& candidates,
+    std::string* signaling_message);
+
+bool ParseJsonSignalingMessage(const std::string& signaling_message,
+                               cricket::SessionDescription** sdp,
+                               std::vector<cricket::Candidate>* candidates);
+}
+
+#endif  // TALK_APP_WEBRTC_WEBRTCJSON_H_
diff --git a/talk/app/webrtcv1/webrtcsession.cc b/talk/app/webrtcv1/webrtcsession.cc
new file mode 100644
index 0000000..d30ee43
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcsession.cc
@@ -0,0 +1,543 @@
+/*
+ * 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/webrtcv1/webrtcsession.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/voicechannel.h"
+
+namespace webrtc {
+
+enum {
+  MSG_CANDIDATE_TIMEOUT = 101,
+};
+
+static const int kAudioMonitorPollFrequency = 100;
+static const int kMonitorPollFrequency = 1000;
+
+// We allow 30 seconds to establish a connection; beyond that we consider
+// it an error
+static const int kCallSetupTimeout = 30 * 1000;
+// A loss of connectivity is probably due to the Internet connection going
+// down, and it might take a while to come back on wireless networks, so we
+// use a longer timeout for that.
+static const int kCallLostTimeout = 60 * 1000;
+
+static const char kVideoStream[] = "video_rtp";
+static const char kAudioStream[] = "rtp";
+
+static const int kDefaultVideoCodecId = 100;
+static const int kDefaultVideoCodecFramerate = 30;
+static const char kDefaultVideoCodecName[] = "VP8";
+
+WebRtcSession::WebRtcSession(const std::string& id,
+                             bool incoming,
+                             cricket::PortAllocator* allocator,
+                             cricket::ChannelManager* channelmgr,
+                             talk_base::Thread* signaling_thread)
+    : BaseSession(signaling_thread, channelmgr->worker_thread(),
+                  allocator, id, "", !incoming),
+      transport_(NULL),
+      channel_manager_(channelmgr),
+      transports_writable_(false),
+      muted_(false),
+      camera_muted_(false),
+      setup_timeout_(kCallSetupTimeout),
+      signaling_thread_(signaling_thread),
+      incoming_(incoming),
+      port_allocator_(allocator),
+      desc_factory_(channel_manager_) {
+}
+
+WebRtcSession::~WebRtcSession() {
+  RemoveAllStreams();
+  // TODO: Do we still need Terminate?
+  // if (state_ != STATE_RECEIVEDTERMINATE) {
+  //   Terminate();
+  // }
+  if (transport_) {
+    delete transport_;
+    transport_ = NULL;
+  }
+}
+
+bool WebRtcSession::Initiate() {
+  const cricket::VideoCodec default_codec(kDefaultVideoCodecId,
+      kDefaultVideoCodecName, kDefaultVideoCodecWidth, kDefaultVideoCodecHeight,
+      kDefaultVideoCodecFramerate, 0);
+  channel_manager_->SetDefaultVideoEncoderConfig(
+      cricket::VideoEncoderConfig(default_codec));
+
+  if (signaling_thread_ == NULL)
+    return false;
+
+  transport_ = CreateTransport();
+
+  if (transport_ == NULL)
+    return false;
+
+  transport_->set_allow_local_ips(true);
+
+  // start transports
+  transport_->SignalRequestSignaling.connect(
+      this, &WebRtcSession::OnRequestSignaling);
+  transport_->SignalCandidatesReady.connect(
+      this, &WebRtcSession::OnCandidatesReady);
+  transport_->SignalWritableState.connect(
+      this, &WebRtcSession::OnWritableState);
+  // Limit the amount of time that setting up a call may take.
+  StartTransportTimeout(kCallSetupTimeout);
+  // Set default secure option to SEC_REQUIRED.
+  desc_factory_.set_secure(cricket::SEC_REQUIRED);
+  return true;
+}
+
+cricket::Transport* WebRtcSession::CreateTransport() {
+  ASSERT(signaling_thread()->IsCurrent());
+  return new cricket::P2PTransport(
+      talk_base::Thread::Current(),
+      channel_manager_->worker_thread(), port_allocator());
+}
+
+bool WebRtcSession::CreateVoiceChannel(const std::string& stream_id) {
+  // RTCP disabled
+  cricket::VoiceChannel* voice_channel =
+      channel_manager_->CreateVoiceChannel(this, stream_id, true);
+  if (voice_channel == NULL) {
+    LOG(LERROR) << "Unable to create voice channel.";
+    return false;
+  }
+  StreamInfo* stream_info = new StreamInfo(stream_id);
+  stream_info->channel = voice_channel;
+  stream_info->video = false;
+  streams_.push_back(stream_info);
+  return true;
+}
+
+bool WebRtcSession::CreateVideoChannel(const std::string& stream_id) {
+  // RTCP disabled
+  cricket::VideoChannel* video_channel =
+      channel_manager_->CreateVideoChannel(this, stream_id, true, NULL);
+  if (video_channel == NULL) {
+    LOG(LERROR) << "Unable to create video channel.";
+    return false;
+  }
+  StreamInfo* stream_info = new StreamInfo(stream_id);
+  stream_info->channel = video_channel;
+  stream_info->video = true;
+  streams_.push_back(stream_info);
+  return true;
+}
+
+cricket::TransportChannel* WebRtcSession::CreateChannel(
+    const std::string& content_name,
+    const std::string& name) {
+  if (!transport_) {
+    return NULL;
+  }
+  std::string type;
+  if (content_name.compare(kVideoStream) == 0) {
+    type = cricket::NS_GINGLE_VIDEO;
+  } else {
+    type = cricket::NS_GINGLE_AUDIO;
+  }
+  cricket::TransportChannel* transport_channel =
+      transport_->CreateChannel(name, type);
+  ASSERT(transport_channel != NULL);
+  return transport_channel;
+}
+
+cricket::TransportChannel* WebRtcSession::GetChannel(
+    const std::string& content_name, const std::string& name) {
+  if (!transport_)
+    return NULL;
+
+  return transport_->GetChannel(name);
+}
+
+void WebRtcSession::DestroyChannel(
+    const std::string& content_name, const std::string& name) {
+  if (!transport_)
+    return;
+
+  transport_->DestroyChannel(name);
+}
+
+void WebRtcSession::OnMessage(talk_base::Message* message) {
+  switch (message->message_id) {
+    case MSG_CANDIDATE_TIMEOUT:
+      if (transport_->writable()) {
+        // This should never happen: The timout triggered even
+        // though a call was successfully set up.
+        ASSERT(false);
+      }
+      SignalFailedCall();
+      break;
+    default:
+      cricket::BaseSession::OnMessage(message);
+      break;
+  }
+}
+
+bool WebRtcSession::Connect() {
+  if (streams_.empty()) {
+    // nothing to initiate
+    return false;
+  }
+  // lets connect all the transport channels created before for this session
+  transport_->ConnectChannels();
+
+  // create an offer now. This is to call SetState
+  // Actual offer will be send when OnCandidatesReady callback received
+  cricket::SessionDescription* offer = CreateOffer();
+  set_local_description(offer);
+  SetState((incoming()) ? STATE_SENTACCEPT : STATE_SENTINITIATE);
+
+  // Enable all the channels
+  EnableAllStreams();
+  return SetVideoCapture(true);
+}
+
+bool WebRtcSession::SetVideoRenderer(const std::string& stream_id,
+                                     cricket::VideoRenderer* renderer) {
+  bool ret = false;
+  StreamMap::iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    StreamInfo* stream_info = (*iter);
+    if (stream_info->stream_id.compare(stream_id) == 0) {
+      ASSERT(stream_info->channel != NULL);
+      ASSERT(stream_info->video);
+      cricket::VideoChannel* channel = static_cast<cricket::VideoChannel*>(
+          stream_info->channel);
+      ret = channel->SetRenderer(0, renderer);
+      break;
+    }
+  }
+  return ret;
+}
+
+bool WebRtcSession::SetVideoCapture(bool capture) {
+  cricket::CaptureResult ret = channel_manager_->SetVideoCapture(capture);
+  if (ret != cricket::CR_SUCCESS && ret != cricket::CR_PENDING) {
+    LOG(LS_ERROR) << "Failed to SetVideoCapture(" << capture << ").";
+    return false;
+  }
+  return true;
+}
+
+bool WebRtcSession::RemoveStream(const std::string& stream_id) {
+  bool ret = false;
+  StreamMap::iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    StreamInfo* sinfo = (*iter);
+    if (sinfo->stream_id.compare(stream_id) == 0) {
+      if (!sinfo->video) {
+        cricket::VoiceChannel* channel = static_cast<cricket::VoiceChannel*> (
+            sinfo->channel);
+        channel->Enable(false);
+        // Note: If later the channel is used by multiple streams, then we
+        // should not destroy the channel until all the streams are removed.
+        channel_manager_->DestroyVoiceChannel(channel);
+      } else {
+        cricket::VideoChannel* channel = static_cast<cricket::VideoChannel*> (
+            sinfo->channel);
+        channel->Enable(false);
+        // Note: If later the channel is used by multiple streams, then we
+        // should not destroy the channel until all the streams are removed.
+        channel_manager_->DestroyVideoChannel(channel);
+      }
+      // channel and transport will be deleted in
+      // DestroyVoiceChannel/DestroyVideoChannel
+      streams_.erase(iter);
+      ret = true;
+      break;
+    }
+  }
+  if (!ret) {
+    LOG(LERROR) << "No streams found for stream id " << stream_id;
+    // TODO: trigger onError callback
+  }
+  return ret;
+}
+
+void WebRtcSession::EnableAllStreams() {
+  StreamMap::const_iterator i;
+  for (i = streams_.begin(); i != streams_.end(); ++i) {
+    cricket::BaseChannel* channel = (*i)->channel;
+    if (channel)
+      channel->Enable(true);
+  }
+}
+
+void WebRtcSession::RemoveAllStreams() {
+  SetState(STATE_RECEIVEDTERMINATE);
+
+  // signaling_thread_->Post(this, MSG_RTC_REMOVEALLSTREAMS);
+  // First build a list of streams to remove and then remove them.
+  // The reason we do this is that if we remove the streams inside the
+  // loop, a stream might get removed while we're enumerating and the iterator
+  // will become invalid (and we crash).
+  // streams_ entry will be removed from ChannelManager callback method
+  // DestroyChannel
+  std::vector<std::string> streams_to_remove;
+  StreamMap::iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter)
+    streams_to_remove.push_back((*iter)->stream_id);
+
+  for (std::vector<std::string>::iterator i = streams_to_remove.begin();
+       i != streams_to_remove.end(); ++i) {
+    RemoveStream(*i);
+  }
+}
+
+bool WebRtcSession::HasStream(const std::string& stream_id) const {
+  StreamMap::const_iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    StreamInfo* sinfo = (*iter);
+    if (stream_id.compare(sinfo->stream_id) == 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool WebRtcSession::HasChannel(bool video) const {
+  StreamMap::const_iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    StreamInfo* sinfo = (*iter);
+    if (sinfo->video == video) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool WebRtcSession::HasAudioChannel() const {
+  return HasChannel(false);
+}
+
+bool WebRtcSession::HasVideoChannel() const {
+  return HasChannel(true);
+}
+
+void WebRtcSession::OnRequestSignaling(cricket::Transport* transport) {
+  transport->OnSignalingReady();
+}
+
+void WebRtcSession::OnWritableState(cricket::Transport* transport) {
+  ASSERT(transport == transport_);
+  const bool transports_writable = transport_->writable();
+  if (transports_writable) {
+    if (transports_writable != transports_writable_) {
+      signaling_thread_->Clear(this, MSG_CANDIDATE_TIMEOUT);
+    } else {
+      // At one point all channels were writable and we had full connectivity,
+      // but then we lost it. Start the timeout again to kill the call if it
+      // doesn't come back.
+      StartTransportTimeout(kCallLostTimeout);
+    }
+    transports_writable_ = transports_writable;
+  }
+  NotifyTransportState();
+  return;
+}
+
+void WebRtcSession::StartTransportTimeout(int timeout) {
+  talk_base::Thread::Current()->PostDelayed(timeout, this,
+                                            MSG_CANDIDATE_TIMEOUT,
+                                            NULL);
+}
+
+void WebRtcSession::NotifyTransportState() {
+}
+
+bool WebRtcSession::OnInitiateMessage(
+    cricket::SessionDescription* offer,
+    const std::vector<cricket::Candidate>& candidates) {
+  if (!offer) {
+    LOG(LERROR) << "No SessionDescription from peer";
+    return false;
+  }
+
+  // Get capabilities from offer before generating an answer to it.
+  cricket::MediaSessionOptions options;
+  if (GetFirstAudioContent(offer))
+    options.has_audio = true;
+  if (GetFirstVideoContent(offer))
+    options.has_video = true;
+
+  talk_base::scoped_ptr<cricket::SessionDescription> answer;
+  answer.reset(CreateAnswer(offer, options));
+
+  if (!answer.get()) {
+    return false;
+  }
+
+  const cricket::ContentInfo* audio_content = GetFirstAudioContent(
+      answer.get());
+  const cricket::ContentInfo* video_content = GetFirstVideoContent(
+      answer.get());
+
+  if (!audio_content && !video_content) {
+    return false;
+  }
+
+  bool ret = true;
+  if (audio_content) {
+    ret = !HasAudioChannel() &&
+          CreateVoiceChannel(audio_content->name);
+    if (!ret) {
+      LOG(LERROR) << "Failed to create voice channel for "
+                  << audio_content->name;
+      return false;
+    }
+  }
+
+  if (video_content) {
+    ret = !HasVideoChannel() &&
+          CreateVideoChannel(video_content->name);
+    if (!ret) {
+      LOG(LERROR) << "Failed to create video channel for "
+                  << video_content->name;
+      return false;
+    }
+  }
+  // Provide remote candidates to the transport
+  transport_->OnRemoteCandidates(candidates);
+
+  set_remote_description(offer);
+  SetState(STATE_RECEIVEDINITIATE);
+
+  transport_->ConnectChannels();
+  EnableAllStreams();
+  SetVideoCapture(true);
+
+  set_local_description(answer.release());
+
+  // AddStream called only once with Video label
+  if (video_content) {
+    SignalAddStream(video_content->name, true);
+  } else {
+    SignalAddStream(audio_content->name, false);
+  }
+  SetState(STATE_SENTACCEPT);
+  return true;
+}
+
+bool WebRtcSession::OnRemoteDescription(
+    cricket::SessionDescription* desc,
+    const std::vector<cricket::Candidate>& candidates) {
+  if (state() == STATE_SENTACCEPT ||
+      state() == STATE_RECEIVEDACCEPT ||
+      state() == STATE_INPROGRESS) {
+    transport_->OnRemoteCandidates(candidates);
+    return true;
+  }
+  // Session description is always accepted.
+  set_remote_description(desc);
+  SetState(STATE_RECEIVEDACCEPT);
+  // Will trigger OnWritableState() if successful.
+  transport_->OnRemoteCandidates(candidates);
+
+  if (!incoming()) {
+    // Trigger OnAddStream callback at the initiator
+    const cricket::ContentInfo* video_content = GetFirstVideoContent(desc);
+    if (video_content && !SendSignalAddStream(true)) {
+      LOG(LERROR) << "Video stream unexpected in answer.";
+      return false;
+    } else {
+      const cricket::ContentInfo* audio_content = GetFirstAudioContent(desc);
+      if (audio_content && !SendSignalAddStream(false)) {
+        LOG(LERROR) << "Audio stream unexpected in answer.";
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+// Send the SignalAddStream with the stream_id based on the content type.
+bool WebRtcSession::SendSignalAddStream(bool video) {
+  StreamMap::const_iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    StreamInfo* sinfo = (*iter);
+    if (sinfo->video == video) {
+      SignalAddStream(sinfo->stream_id, video);
+      return true;
+    }
+  }
+  return false;
+}
+
+cricket::SessionDescription* WebRtcSession::CreateOffer() {
+  cricket::MediaSessionOptions options;
+  options.has_audio = false;  // disable default option
+  StreamMap::const_iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    if ((*iter)->video) {
+      options.has_video = true;
+    } else {
+      options.has_audio = true;
+    }
+  }
+  // We didn't save the previous offer.
+  const cricket::SessionDescription* previous_offer = NULL;
+  return desc_factory_.CreateOffer(options, previous_offer);
+}
+
+cricket::SessionDescription* WebRtcSession::CreateAnswer(
+    const cricket::SessionDescription* offer,
+    const cricket::MediaSessionOptions& options) {
+  // We didn't save the previous answer.
+  const cricket::SessionDescription* previous_answer = NULL;
+  return desc_factory_.CreateAnswer(offer, options, previous_answer);
+}
+
+void WebRtcSession::SetError(Error error) {
+  BaseSession::SetError(error);
+}
+
+void WebRtcSession::OnCandidatesReady(
+    cricket::Transport* transport,
+    const std::vector<cricket::Candidate>& candidates) {
+  std::vector<cricket::Candidate>::const_iterator iter;
+  for (iter = candidates.begin(); iter != candidates.end(); ++iter) {
+    local_candidates_.push_back(*iter);
+  }
+  SignalLocalDescription(local_description(), candidates);
+}
+} /* namespace webrtc */
diff --git a/talk/app/webrtcv1/webrtcsession.h b/talk/app/webrtcv1/webrtcsession.h
new file mode 100644
index 0000000..8f6c6ad
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcsession.h
@@ -0,0 +1,210 @@
+/*
+ * 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_WEBRTCSESSION_H_
+#define TALK_APP_WEBRTC_WEBRTCSESSION_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/logging.h"
+#include "talk/base/messagehandler.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/session.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/mediasession.h"
+
+namespace cricket {
+class ChannelManager;
+class Transport;
+class TransportChannel;
+class VoiceChannel;
+class VideoChannel;
+struct ConnectionInfo;
+}
+
+namespace Json {
+class Value;
+}
+
+namespace webrtc {
+
+typedef std::vector<cricket::AudioCodec> AudioCodecs;
+typedef std::vector<cricket::VideoCodec> VideoCodecs;
+
+class WebRtcSession : public cricket::BaseSession {
+ public:
+  WebRtcSession(const std::string& id,
+                    bool incoming,
+                    cricket::PortAllocator* allocator,
+                    cricket::ChannelManager* channelmgr,
+                    talk_base::Thread* signaling_thread);
+
+  ~WebRtcSession();
+
+  bool Initiate();
+  bool Connect();
+  bool OnRemoteDescription(cricket::SessionDescription* sdp,
+      const std::vector<cricket::Candidate>& candidates);
+  bool OnInitiateMessage(cricket::SessionDescription* sdp,
+      const std::vector<cricket::Candidate>& candidates);
+  bool CreateVoiceChannel(const std::string& stream_id);
+  bool CreateVideoChannel(const std::string& stream_id);
+  bool RemoveStream(const std::string& stream_id);
+  void RemoveAllStreams();
+
+  // Returns true if we have either a voice or video stream matching this label.
+  bool HasStream(const std::string& label) const;
+  bool HasChannel(bool video) const;
+
+  // Returns true if there's one or more audio channels in the session.
+  bool HasAudioChannel() const;
+
+  // Returns true if there's one or more video channels in the session.
+  bool HasVideoChannel() const;
+
+  bool SetVideoRenderer(const std::string& stream_id,
+                        cricket::VideoRenderer* renderer);
+
+  // This signal occurs when all the streams have been removed.
+  // It is triggered by a successful call to the RemoveAllStream or
+  // the OnRemoteDescription with stream deleted signaling message with the
+  // candidates port equal to 0.
+  sigslot::signal1<WebRtcSession*> SignalRemoveStreamMessage;
+
+  // This signal indicates a stream has been added properly.
+  // It is triggered by a successful call to the OnInitiateMessage or
+  // the OnRemoteDescription and if it's going to the STATE_RECEIVEDACCEPT.
+  sigslot::signal2<const std::string&, bool> SignalAddStream;
+
+  // This signal occurs when one stream is removed with the signaling
+  // message from the remote peer with the candidates port equal to 0.
+  sigslot::signal2<const std::string&, bool> SignalRemoveStream;
+
+  // This signal occurs when the local candidate is ready
+  sigslot::signal2<const cricket::SessionDescription*,
+      const std::vector<cricket::Candidate>&> SignalLocalDescription;
+
+  // This signal triggers when setting up or resuming a call has not been
+  // successful before a certain time out.
+  sigslot::signal0<> SignalFailedCall;
+
+  bool muted() const { return muted_; }
+  bool camera_muted() const { return camera_muted_; }
+  const std::vector<cricket::Candidate>& local_candidates() {
+    return local_candidates_;
+  }
+  void set_incoming(bool incoming) { incoming_ = incoming; }
+  bool incoming() const { return incoming_; }
+  cricket::PortAllocator* port_allocator() const { return port_allocator_; }
+  talk_base::Thread* signaling_thread() const { return signaling_thread_; }
+
+  static const int kDefaultVideoCodecWidth = 640;
+  static const int kDefaultVideoCodecHeight = 480;
+
+ protected:
+  // methods from cricket::BaseSession
+  virtual void SetError(cricket::BaseSession::Error error);
+  virtual cricket::TransportChannel* CreateChannel(
+      const std::string& content_name, const std::string& name);
+  virtual cricket::TransportChannel* GetChannel(
+      const std::string& content_name, const std::string& name);
+  virtual void DestroyChannel(
+      const std::string& content_name, const std::string& name);
+
+ private:
+  struct StreamInfo {
+    explicit StreamInfo(const std::string stream_id)
+        : channel(NULL),
+          video(false),
+          stream_id(stream_id) {}
+
+    StreamInfo()
+        : channel(NULL),
+          video(false) {}
+    cricket::BaseChannel* channel;
+    bool video;
+    std::string stream_id;
+  };
+  // Not really a map (vector).
+  typedef std::vector<StreamInfo*> StreamMap;
+
+  // methods signaled by the transport
+  void OnRequestSignaling(cricket::Transport* transport);
+  void OnCandidatesReady(cricket::Transport* transport,
+                         const std::vector<cricket::Candidate>& candidates);
+  void OnWritableState(cricket::Transport* transport);
+  void OnTransportError(cricket::Transport* transport);
+  void OnChannelGone(cricket::Transport* transport);
+
+  bool CheckForStreamDeleteMessage(
+      const std::vector<cricket::Candidate>& candidates);
+  void ProcessTerminateAccept(cricket::SessionDescription* desc);
+
+  void UpdateTransportWritableState();
+  bool CheckAllTransportsWritable();
+  void StartTransportTimeout(int timeout);
+  void NotifyTransportState();
+
+  cricket::SessionDescription* CreateOffer();
+  cricket::SessionDescription* CreateAnswer(
+      const cricket::SessionDescription* answer,
+      const cricket::MediaSessionOptions& options);
+
+  // from MessageHandler
+  virtual void OnMessage(talk_base::Message* message);
+
+  virtual cricket::Transport* CreateTransport();
+  cricket::Transport* GetTransport();
+
+  typedef std::map<std::string, cricket::TransportChannel*> TransportChannelMap;
+
+  bool SetVideoCapture(bool capture);
+  void EnableAllStreams();
+  bool SendSignalAddStream(bool video);
+
+  cricket::Transport* transport_;
+  cricket::ChannelManager* channel_manager_;
+  std::vector<StreamInfo*> streams_;
+  TransportChannelMap transport_channels_;
+  bool transports_writable_;
+  bool muted_;
+  bool camera_muted_;
+  int setup_timeout_;
+  std::vector<cricket::Candidate> local_candidates_;
+
+  talk_base::Thread* signaling_thread_;
+  bool incoming_;
+  cricket::PortAllocator* port_allocator_;
+  cricket::MediaSessionDescriptionFactory desc_factory_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_WEBRTCSESSION_H_
diff --git a/talk/app/webrtcv1/webrtcsession_unittest.cc b/talk/app/webrtcv1/webrtcsession_unittest.cc
new file mode 100644
index 0000000..6f27fb4
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcsession_unittest.cc
@@ -0,0 +1,475 @@
+/*
+ * 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 <stdio.h>
+
+#include <list>
+
+#include "talk/app/webrtcv1/unittest_utilities.h"
+#include "talk/app/webrtcv1/webrtcsession.h"
+#include "talk/base/fakenetwork.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/client/fakeportallocator.h"
+#include "talk/session/phone/dummydevicemanager.h"
+#include "talk/session/phone/fakewebrtcvcmfactory.h"
+#include "talk/session/phone/fakewebrtcvideocapturemodule.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/webrtcmediaengine.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+class WebRtcSessionTest
+    : public sigslot::has_slots<>,
+      public testing::Test {
+ public:
+  enum CallbackId {
+    kNone,
+    kOnAddStream,
+    kOnRemoveStream,
+    kOnLocalDescription,
+    kOnFailedCall,
+  };
+
+  WebRtcSessionTest()
+      : last_was_video_(false),
+        last_description_ptr_(NULL),
+        session_(NULL),
+        receiving_(false),
+        allocator_(NULL),
+        channel_manager_(NULL),
+        video_capturer_(NULL),
+        worker_thread_(NULL),
+        signaling_thread_(NULL) {
+  }
+
+  ~WebRtcSessionTest() {
+    session_.reset();
+    // Ensure the VideoCapturer be unregistered before destroyed.
+    channel_manager_->SetVideoCapturer(NULL, 0);
+  }
+
+  void OnAddStream(const std::string& stream_id, bool video) {
+    callback_ids_.push_back(kOnAddStream);
+    last_stream_id_ = stream_id;
+    last_was_video_ = video;
+  }
+  void OnRemoveStream(const std::string& stream_id, bool video) {
+    callback_ids_.push_back(kOnRemoveStream);
+    last_stream_id_ = stream_id;
+    last_was_video_ = video;
+  }
+  void OnLocalDescription(
+      const cricket::SessionDescription* desc,
+      const std::vector<cricket::Candidate>& candidates) {
+    callback_ids_.push_back(kOnLocalDescription);
+    last_description_ptr_.reset(CopySessionDescription(desc));
+    CopyCandidates(candidates, &last_candidates_);
+  }
+  cricket::SessionDescription* GetLocalDescription(
+      std::vector<cricket::Candidate>* candidates) {
+    if (last_candidates_.empty()) {
+      return NULL;
+    }
+    if (!last_description_ptr_.get()) {
+      return NULL;
+    }
+    CopyCandidates(last_candidates_, candidates);
+    return CopySessionDescription(last_description_ptr_.get());
+  }
+
+  void OnFailedCall() {
+    callback_ids_.push_back(kOnFailedCall);
+  }
+
+  CallbackId PopOldestCallback() {
+    if (callback_ids_.empty()) {
+      return kNone;
+    }
+    const CallbackId return_value = callback_ids_.front();
+    callback_ids_.pop_front();
+    return return_value;
+  }
+
+  CallbackId PeekOldestCallback() {
+    if (callback_ids_.empty()) {
+      return kNone;
+    }
+    const CallbackId return_value = callback_ids_.front();
+    return return_value;
+  }
+
+  void Reset() {
+    callback_ids_.clear();
+    last_stream_id_ = "";
+    last_was_video_ = false;
+    last_description_ptr_.reset();
+    last_candidates_.clear();
+  }
+
+  bool WaitForCallback(CallbackId id, int timeout_ms) {
+    bool success = false;
+    for (int ms = 0; ms < timeout_ms; ms++) {
+      const CallbackId peek_id = PeekOldestCallback();
+      if (peek_id == id) {
+        PopOldestCallback();
+        success = true;
+        break;
+      } else if (peek_id != kNone) {
+        success = false;
+        break;
+      }
+      talk_base::Thread::Current()->ProcessMessages(1);
+    }
+    return success;
+  }
+
+  bool Init(bool receiving) {
+    if (signaling_thread_ != NULL)
+        return false;
+    signaling_thread_ = talk_base::Thread::Current();
+    receiving_ = receiving;
+
+    if (worker_thread_!= NULL)
+        return false;
+    worker_thread_ = talk_base::Thread::Current();
+
+    cricket::FakePortAllocator* fake_port_allocator =
+        new cricket::FakePortAllocator(worker_thread_, NULL);
+
+    allocator_.reset(static_cast<cricket::PortAllocator*>(fake_port_allocator));
+
+    cricket::DummyDeviceManager* device_manager(
+        new cricket::DummyDeviceManager());
+    cricket::WebRtcMediaEngine* webrtc_media_engine(
+        new cricket::WebRtcMediaEngine(NULL, NULL, NULL));
+    channel_manager_.reset(new cricket::ChannelManager(webrtc_media_engine,
+                                                       device_manager,
+                                                       worker_thread_));
+    if (!channel_manager_->Init())
+      return false;
+
+    FakeWebRtcVideoCaptureModule* vcm =
+        new FakeWebRtcVideoCaptureModule(NULL, 123);
+    video_capturer_.reset(new cricket::WebRtcVideoCapturer);
+    if (!video_capturer_->Init(vcm)) {
+      return false;
+    }
+    // The SetVideoCapturer call doesn't transfer ownership.
+    if (!channel_manager_->SetVideoCapturer(video_capturer_.get(), 0))
+      return false;
+
+    talk_base::CreateRandomString(8, &id_);
+
+    session_.reset(new webrtc::WebRtcSession(
+        id_, receiving_ , allocator_.get(),
+        channel_manager_.get(),
+        signaling_thread_));
+    session_->SignalAddStream.connect(this, &WebRtcSessionTest::OnAddStream);
+    session_->SignalRemoveStream.connect(this,
+        &WebRtcSessionTest::OnRemoveStream);
+    session_->SignalLocalDescription.connect(this,
+        &WebRtcSessionTest::OnLocalDescription);
+    session_->SignalFailedCall.connect(this, &WebRtcSessionTest::OnFailedCall);
+    return true;
+  }
+
+  // All session APIs must be called from the signaling thread.
+  bool CallInitiate() {
+    return session_->Initiate();
+  }
+
+  bool CallConnect() {
+    if (!session_->Connect())
+      return false;
+    // This callback does not happen with FakeTransport!
+    if (!WaitForCallback(kOnLocalDescription, 1000)) {
+      return false;
+    }
+    return true;
+  }
+
+  bool CallOnRemoteDescription(
+      cricket::SessionDescription* description,
+      std::vector<cricket::Candidate> candidates) {
+    if (!session_->OnRemoteDescription(description, candidates)) {
+      return false;
+    }
+    if (!WaitForCallback(kOnAddStream, 1000)) {
+      return false;
+    }
+    return true;
+  }
+
+  bool CallOnInitiateMessage(
+      cricket::SessionDescription* description,
+      const std::vector<cricket::Candidate>& candidates) {
+    if (!session_->OnInitiateMessage(description, candidates)) {
+      return false;
+    }
+    if (!WaitForCallback(kOnAddStream, 1000)) {
+      return false;
+    }
+    return true;
+  }
+
+  bool CallCreateVoiceChannel(const std::string& stream_id) {
+    if (!session_->CreateVoiceChannel(stream_id)) {
+      return false;
+    }
+    return true;
+  }
+
+  bool CallCreateVideoChannel(const std::string& stream_id) {
+    if (!session_->CreateVideoChannel(stream_id)) {
+      return false;
+    }
+    return true;
+  }
+
+  bool CallRemoveStream(const std::string& stream_id) {
+    return session_->RemoveStream(stream_id);
+  }
+
+  void CallRemoveAllStreams() {
+    session_->RemoveAllStreams();
+  }
+
+  bool CallHasChannel(const std::string& label) {
+    return session_->HasStream(label);
+  }
+
+  bool CallHasChannel(bool video) {
+    return session_->HasChannel(video);
+  }
+
+  bool CallHasAudioChannel() {
+    return session_->HasAudioChannel();
+  }
+
+  bool CallHasVideoChannel() {
+    return session_->HasVideoChannel();
+  }
+
+  bool CallSetVideoRenderer(const std::string& stream_id,
+                            cricket::VideoRenderer* renderer) {
+    return session_->SetVideoRenderer(stream_id, renderer);
+  }
+
+  const std::vector<cricket::Candidate>& CallLocalCandidates() {
+    return session_->local_candidates();
+  }
+  cricket::ChannelManager* channel_manager() const {
+    return channel_manager_.get();
+  }
+  const cricket::SessionDescription* local_description() const {
+    return session_->local_description();
+  }
+  cricket::SessionDescription* remote_description() const {
+    return session_->remote_description();
+  }
+
+  void VerifyCryptoParams(const cricket::SessionDescription* sdp,
+                          bool offer) {
+    const cricket::ContentInfo* content = cricket::GetFirstAudioContent(sdp);
+    if (content) {
+      const cricket::AudioContentDescription* audio_content =
+          static_cast<const cricket::AudioContentDescription*>(
+              content->description);
+      ASSERT_TRUE(audio_content != NULL);
+      ASSERT_EQ(offer ? 2U : 1U, audio_content->cryptos().size());
+      // key(40) + inline string
+      ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size());
+      ASSERT_EQ("AES_CM_128_HMAC_SHA1_32",
+                audio_content->cryptos()[0].cipher_suite);
+      if (offer) {
+        ASSERT_EQ(47U, audio_content->cryptos()[1].key_params.size());
+        ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+                  audio_content->cryptos()[1].cipher_suite);
+      }
+    }
+    content = cricket::GetFirstVideoContent(sdp);
+    if (content) {
+      ASSERT_TRUE(content != NULL);
+      const cricket::VideoContentDescription* video_content =
+          static_cast<const cricket::VideoContentDescription*>(
+              content->description);
+      ASSERT_TRUE(video_content != NULL);
+      ASSERT_EQ(1U, video_content->cryptos().size());
+      ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+                video_content->cryptos()[0].cipher_suite);
+      ASSERT_EQ(47U, video_content->cryptos()[0].key_params.size());
+    }
+  }
+
+ private:
+  std::list<CallbackId> callback_ids_;
+
+  std::string last_stream_id_;
+  bool last_was_video_;
+  talk_base::scoped_ptr<cricket::SessionDescription> last_description_ptr_;
+  std::vector<cricket::Candidate> last_candidates_;
+
+  talk_base::scoped_ptr<webrtc::WebRtcSession> session_;
+  std::string id_;
+  bool receiving_;
+
+  talk_base::scoped_ptr<cricket::PortAllocator> allocator_;
+
+  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+  talk_base::scoped_ptr<cricket::WebRtcVideoCapturer> video_capturer_;
+
+  talk_base::Thread* worker_thread_;
+  talk_base::Thread* signaling_thread_;
+};
+
+bool CallbackReceived(WebRtcSessionTest* session, int timeout) {
+  EXPECT_EQ_WAIT(WebRtcSessionTest::kNone, session->PeekOldestCallback(),
+                 timeout);
+  const WebRtcSessionTest::CallbackId peek_id =
+      session->PeekOldestCallback();
+  return peek_id != WebRtcSessionTest::kNone;
+}
+
+TEST_F(WebRtcSessionTest, InitializationReceiveSanity) {
+  const bool kReceiving = true;
+  ASSERT_TRUE(Init(kReceiving));
+  ASSERT_TRUE(CallInitiate());
+
+  // Should return false because no stream has been set up yet.
+  EXPECT_FALSE(CallConnect());
+  const bool kVideo = true;
+  EXPECT_FALSE(CallHasChannel(kVideo));
+  EXPECT_FALSE(CallHasChannel(!kVideo));
+
+  EXPECT_EQ(kNone, PopOldestCallback());
+}
+
+TEST_F(WebRtcSessionTest, AudioSendCallSetUp) {
+  const bool kReceiving = false;
+  ASSERT_TRUE(Init(kReceiving));
+
+  ASSERT_TRUE(CallInitiate());
+
+  ASSERT_TRUE(CallCreateVoiceChannel("Audio"));
+  ASSERT_TRUE(CallConnect());
+
+  std::vector<cricket::Candidate> candidates;
+  cricket::SessionDescription* local_session = GetLocalDescription(
+      &candidates);
+  ASSERT_FALSE(candidates.empty());
+  ASSERT_FALSE(local_session == NULL);
+  if (!CallOnRemoteDescription(local_session, candidates)) {
+      delete local_session;
+      FAIL();
+  }
+
+  // All callbacks should be caught. Assert it.
+  ASSERT_FALSE(CallbackReceived(this, 1000));
+  ASSERT_TRUE(CallHasAudioChannel() &&
+              !CallHasVideoChannel());
+}
+
+TEST_F(WebRtcSessionTest, VideoSendCallSetUp) {
+  const bool kReceiving = false;
+  ASSERT_TRUE(Init(kReceiving));
+
+  ASSERT_TRUE(CallInitiate());
+
+  ASSERT_TRUE(CallCreateVideoChannel("Video"));
+  ASSERT_TRUE(CallConnect());
+
+  std::vector<cricket::Candidate> candidates;
+  cricket::SessionDescription* local_session = GetLocalDescription(
+      &candidates);
+  ASSERT_FALSE(candidates.empty());
+  ASSERT_FALSE(local_session == NULL);
+
+  if (!CallOnRemoteDescription(local_session, candidates)) {
+      delete local_session;
+      FAIL();
+  }
+
+  // All callbacks should be caught. Assert it.
+  ASSERT_FALSE(CallbackReceived(this, 1000));
+  ASSERT_TRUE(!CallHasAudioChannel() &&
+              CallHasVideoChannel());
+}
+
+TEST_F(WebRtcSessionTest, AudioReceiveCallSetUp) {
+  const bool kReceiving = true;
+  const bool video = false;
+
+  ASSERT_TRUE(Init(kReceiving));
+
+  std::vector<cricket::Candidate> candidates;
+  cricket::SessionDescription* local_session =
+      GenerateFakeSession(channel_manager(), video, &candidates);
+  ASSERT_FALSE(candidates.empty());
+  ASSERT_FALSE(local_session == NULL);
+  ASSERT_TRUE(CallInitiate());
+  if (!CallOnInitiateMessage(local_session, candidates)) {
+    delete local_session;
+    FAIL();
+  }
+
+  ASSERT_FALSE(CallbackReceived(this, 1000));
+  ASSERT_TRUE(CallHasAudioChannel() &&
+              !CallHasVideoChannel());
+  // Incoming call - local desc has the answer.
+  VerifyCryptoParams(local_description(), false);
+  VerifyCryptoParams(remote_description(), true);
+}
+
+TEST_F(WebRtcSessionTest, VideoReceiveCallSetUp) {
+  const bool kReceiving = true;
+  const bool video = true;
+
+  ASSERT_TRUE(Init(kReceiving));
+
+  std::vector<cricket::Candidate> candidates;
+  cricket::SessionDescription* local_session =
+      GenerateFakeSession(channel_manager(), video, &candidates);
+  ASSERT_FALSE(candidates.empty());
+  ASSERT_FALSE(local_session == NULL);
+  ASSERT_TRUE(CallInitiate());
+  if (!CallOnInitiateMessage(local_session, candidates)) {
+    delete local_session;
+    FAIL();
+  }
+
+  ASSERT_FALSE(CallbackReceived(this, 1000));
+  ASSERT_TRUE(!CallHasAudioChannel() &&
+              CallHasVideoChannel());
+  // Incoming call - local desc has the answer.
+  VerifyCryptoParams(local_description(), false);
+  VerifyCryptoParams(remote_description(), true);
+}
diff --git a/talk/app/webrtcv1/webrtcv1.scons b/talk/app/webrtcv1/webrtcv1.scons
new file mode 100644
index 0000000..a648422
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcv1.scons
@@ -0,0 +1,61 @@
+# -*- Python -*-
+import talk
+
+Import('env')
+
+if env.Bit('have_webrtc_voice') and env.Bit('have_webrtc_video'):
+  # local sources
+  talk.Library(
+    env,
+    name = 'webrtcv1',
+    srcs = [
+      'peerconnectionimpl.cc',
+      'peerconnectionproxy.cc',
+      'peerconnectionfactory.cc',
+      'webrtcjson.cc',
+      'webrtcsession.cc',
+    ],
+  )
+
+  talk.Unittest(
+    env,
+    name = 'webrtcv1',
+    srcs = [
+      'peerconnection_unittest.cc',
+      'unittest_utilities.cc',
+      'webrtcsession_unittest.cc',
+    ],
+    libs = [
+      'base',
+      'expat',
+      'jpeg',
+      'json',
+      'p2p',
+      'phone',
+      'srtp',
+      'webrtcv1',
+      'xmpp',
+      'xmllite',
+      'yuvscaler'
+    ],
+    include_talk_media_libs = True,
+    mac_libs = [
+      'crypto',
+      'ssl',
+    ],
+    mac_FRAMEWORKS = [
+      'Foundation',
+      'IOKit',
+      'QTKit',
+    ],
+    win_link_flags = [('', '/nodefaultlib:libcmt')[env.Bit('debug')]],
+    lin_libs = [
+      'rt',
+      'dl',
+      'sound',
+      'X11',
+      'Xext',
+      'Xfixes',
+      'Xrandr'
+    ],
+  )
diff --git a/talk/base/asynchttprequest.h b/talk/base/asynchttprequest.h
index a1e8aa0..13edf61 100644
--- a/talk/base/asynchttprequest.h
+++ b/talk/base/asynchttprequest.h
@@ -55,6 +55,7 @@
   int start_delay() const { return start_delay_; }
   void set_start_delay(int delay) { start_delay_ = delay; }
 
+  const ProxyInfo& proxy() const { return proxy_; }
   void set_proxy(const ProxyInfo& proxy) {
     proxy_ = proxy;
   }
diff --git a/talk/base/atomicops.h b/talk/base/atomicops.h
new file mode 100644
index 0000000..94ade69
--- /dev/null
+++ b/talk/base/atomicops.h
@@ -0,0 +1,166 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_ATOMICOPS_H_
+#define TALK_BASE_ATOMICOPS_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+// A single-producer, single-consumer, fixed-size queue.
+// All methods not ending in Unsafe can be safely called without locking,
+// provided that calls to consumer methods (Peek/Pop) or producer methods (Push)
+// only happen on a single thread per method type. If multiple threads need to
+// read simultaneously or write simultaneously, other synchronization is
+// necessary. Synchronization is also required if a call into any Unsafe method
+// could happen at the same time as a call to any other method.
+template <typename T>
+class FixedSizeLockFreeQueue {
+ private:
+// Atomic primitives and memory barrier
+#if defined(__arm__)
+  typedef uint32 Atomic32;
+
+  // Copied from google3/base/atomicops-internals-arm-v6plus.h
+  static inline void MemoryBarrier() {
+    asm volatile("dmb":::"memory");
+  }
+
+  // Adapted from google3/base/atomicops-internals-arm-v6plus.h
+  static inline void AtomicIncrement(volatile Atomic32* ptr) {
+    Atomic32 str_success, value;
+    asm volatile (
+        "1:\n"
+        "ldrex  %1, [%2]\n"
+        "add    %1, %1, #1\n"
+        "strex  %0, %1, [%2]\n"
+        "teq    %0, #0\n"
+        "bne    1b"
+        : "=&r"(str_success), "=&r"(value)
+        : "r" (ptr)
+        : "cc", "memory");
+  }
+#elif !defined(SKIP_ATOMIC_CHECK)
+#error "No atomic operations defined for the given architecture."
+#endif
+
+ public:
+  // Constructs an empty queue, with capacity 0.
+  FixedSizeLockFreeQueue() : pushed_count_(0),
+                             popped_count_(0),
+                             capacity_(0),
+                             data_(NULL) {}
+  // Constructs an empty queue with the given capacity.
+  FixedSizeLockFreeQueue(size_t capacity) : pushed_count_(0),
+                                            popped_count_(0),
+                                            capacity_(capacity),
+                                            data_(new T[capacity]) {}
+
+  // Pushes a value onto the queue. Returns true if the value was successfully
+  // pushed (there was space in the queue). This method can be safely called at
+  // the same time as PeekFront/PopFront.
+  bool PushBack(T value) {
+    if (capacity_ == 0) {
+      LOG(LS_WARNING) << "Queue capacity is 0.";
+      return false;
+    }
+    if (IsFull()) {
+      return false;
+    }
+
+    data_[pushed_count_ % capacity_] = value;
+    // Make sure the data is written before the count is incremented, so other
+    // threads can't see the value exists before being able to read it.
+    MemoryBarrier();
+    AtomicIncrement(&pushed_count_);
+    return true;
+  }
+
+  // Retrieves the oldest value pushed onto the queue. Returns true if there was
+  // an item to peek (the queue was non-empty). This method can be safely called
+  // at the same time as PushBack.
+  bool PeekFront(T* value_out) {
+    if (capacity_ == 0) {
+      LOG(LS_WARNING) << "Queue capacity is 0.";
+      return false;
+    }
+    if (IsEmpty()) {
+      return false;
+    }
+
+    *value_out = data_[popped_count_ % capacity_];
+    return true;
+  }
+
+  // Retrieves the oldest value pushed onto the queue and removes it from the
+  // queue. Returns true if there was an item to pop (the queue was non-empty).
+  // This method can be safely called at the same time as PushBack.
+  bool PopFront(T* value_out) {
+    if (PeekFront(value_out)) {
+      AtomicIncrement(&popped_count_);
+      return true;
+    }
+    return false;
+  }
+
+  // Clears the current items in the queue and sets the new (fixed) size. This
+  // method cannot be called at the same time as any other method.
+  void ClearAndResizeUnsafe(int new_capacity) {
+    capacity_ = new_capacity;
+    data_.reset(new T[new_capacity]);
+    pushed_count_ = 0;
+    popped_count_ = 0;
+  }
+
+  // Returns true if there is no space left in the queue for new elements.
+  int IsFull() const { return pushed_count_ == popped_count_ + capacity_; }
+  // Returns true if there are no elements in the queue.
+  int IsEmpty() const { return pushed_count_ == popped_count_; }
+  // Returns the current number of elements in the queue. This is always in the
+  // range [0, capacity]
+  size_t Size() const { return pushed_count_ - popped_count_; }
+
+  // Returns the capacity of the queue (max size).
+  size_t capacity() const { return capacity_; }
+
+ private:
+  volatile Atomic32 pushed_count_;
+  volatile Atomic32 popped_count_;
+  size_t capacity_;
+  talk_base::scoped_array<T> data_;
+  DISALLOW_COPY_AND_ASSIGN(FixedSizeLockFreeQueue);
+};
+
+}
+
+#endif  // TALK_BASE_ATOMICOPS_H_
diff --git a/talk/base/atomicops_unittest.cc b/talk/base/atomicops_unittest.cc
new file mode 100644
index 0000000..24804c6
--- /dev/null
+++ b/talk/base/atomicops_unittest.cc
@@ -0,0 +1,96 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if !defined(__arm__)
+// For testing purposes, define faked versions of the atomic operations
+#include "talk/base/basictypes.h"
+namespace talk_base {
+typedef uint32 Atomic32;
+static inline void MemoryBarrier() { }
+static inline void AtomicIncrement(volatile Atomic32* ptr) {
+  *ptr = *ptr + 1;
+}
+}
+#define SKIP_ATOMIC_CHECK
+#endif
+
+#include "talk/base/atomicops.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+
+TEST(FixedSizeLockFreeQueueTest, TestDefaultConstruct) {
+  talk_base::FixedSizeLockFreeQueue<int> queue;
+  EXPECT_EQ(0u, queue.capacity());
+  EXPECT_EQ(0u, queue.Size());
+  EXPECT_FALSE(queue.PushBack(1));
+  int val;
+  EXPECT_FALSE(queue.PopFront(&val));
+}
+
+TEST(FixedSizeLockFreeQueueTest, TestConstruct) {
+  talk_base::FixedSizeLockFreeQueue<int> queue(5);
+  EXPECT_EQ(5u, queue.capacity());
+  EXPECT_EQ(0u, queue.Size());
+  int val;
+  EXPECT_FALSE(queue.PopFront(&val));
+}
+
+TEST(FixedSizeLockFreeQueueTest, TestPushPop) {
+  talk_base::FixedSizeLockFreeQueue<int> queue(2);
+  EXPECT_EQ(2u, queue.capacity());
+  EXPECT_EQ(0u, queue.Size());
+  EXPECT_TRUE(queue.PushBack(1));
+  EXPECT_EQ(1u, queue.Size());
+  EXPECT_TRUE(queue.PushBack(2));
+  EXPECT_EQ(2u, queue.Size());
+  EXPECT_FALSE(queue.PushBack(3));
+  EXPECT_EQ(2u, queue.Size());
+  int val;
+  EXPECT_TRUE(queue.PopFront(&val));
+  EXPECT_EQ(1, val);
+  EXPECT_EQ(1u, queue.Size());
+  EXPECT_TRUE(queue.PopFront(&val));
+  EXPECT_EQ(2, val);
+  EXPECT_EQ(0u, queue.Size());
+  EXPECT_FALSE(queue.PopFront(&val));
+  EXPECT_EQ(0u, queue.Size());
+}
+
+TEST(FixedSizeLockFreeQueueTest, TestResize) {
+  talk_base::FixedSizeLockFreeQueue<int> queue(2);
+  EXPECT_EQ(2u, queue.capacity());
+  EXPECT_EQ(0u, queue.Size());
+  EXPECT_TRUE(queue.PushBack(1));
+  EXPECT_EQ(1u, queue.Size());
+
+  queue.ClearAndResizeUnsafe(5);
+  EXPECT_EQ(5u, queue.capacity());
+  EXPECT_EQ(0u, queue.Size());
+  int val;
+  EXPECT_FALSE(queue.PopFront(&val));
+}
diff --git a/talk/base/autodetectproxy.cc b/talk/base/autodetectproxy.cc
index f79926d..37faa4d 100644
--- a/talk/base/autodetectproxy.cc
+++ b/talk/base/autodetectproxy.cc
@@ -107,7 +107,7 @@
                       proxy().address.hostname(),
                       sizeof address_hostname);
 
-    uint32 address_ip = proxy().address.ip();
+    IPAddress address_ip = proxy().address.ipaddr();
 
     uint16 address_port = proxy().address.port();
 
diff --git a/talk/base/bandwidthsmoother.cc b/talk/base/bandwidthsmoother.cc
new file mode 100644
index 0000000..130f2a8
--- /dev/null
+++ b/talk/base/bandwidthsmoother.cc
@@ -0,0 +1,90 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/bandwidthsmoother.h"
+
+#include <limits.h>
+
+namespace talk_base {
+
+BandwidthSmoother::BandwidthSmoother(int initial_bandwidth_guess,
+                                     uint32 time_between_increase,
+                                     double percent_increase,
+                                     size_t samples_count_to_average)
+    : time_between_increase_(time_between_increase),
+      percent_increase_(talk_base::_max(1.0, percent_increase)),
+      time_at_last_change_(0),
+      bandwidth_estimation_(initial_bandwidth_guess),
+      accumulator_(samples_count_to_average) {
+}
+
+// Samples a new bandwidth measurement
+// returns true if the bandwidth estimation changed
+bool BandwidthSmoother::Sample(uint32 sample_time, int bandwidth) {
+  if (bandwidth < 0) {
+    return false;
+  }
+
+  accumulator_.AddSample(bandwidth);
+
+  // Replace bandwidth with the mean of sampled bandwidths.
+  bandwidth = accumulator_.ComputeMean();
+
+  if (bandwidth < bandwidth_estimation_) {
+    time_at_last_change_ = sample_time;
+    bandwidth_estimation_ = bandwidth;
+    return true;
+  }
+
+  const int old_bandwidth_estimation = bandwidth_estimation_;
+  const double increase_threshold_d = percent_increase_ * bandwidth_estimation_;
+  if (increase_threshold_d > INT_MAX) {
+    // If bandwidth goes any higher we would overflow.
+    return false;
+  }
+
+  const int increase_threshold = static_cast<int>(increase_threshold_d);
+  if (bandwidth < increase_threshold) {
+    time_at_last_change_ = sample_time;
+    // The value of bandwidth_estimation remains the same if we don't exceed
+    // percent_increase_ * bandwidth_estimation_ for at least
+    // time_between_increase_ time.
+  } else if (sample_time >= time_at_last_change_ + time_between_increase_) {
+    time_at_last_change_ = sample_time;
+    if (increase_threshold == 0) {
+      bandwidth_estimation_ = bandwidth / 2;
+    } else {
+      bandwidth_estimation_ = increase_threshold;
+    }
+  }
+  // Else don't make a change.
+
+  return old_bandwidth_estimation != bandwidth_estimation_;
+}
+
+}  // namespace talk_base
+
diff --git a/talk/base/bandwidthsmoother.h b/talk/base/bandwidthsmoother.h
new file mode 100644
index 0000000..afd1e52
--- /dev/null
+++ b/talk/base/bandwidthsmoother.h
@@ -0,0 +1,71 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_BANDWIDTHSMOOTHER_H_
+#define TALK_BASE_BANDWIDTHSMOOTHER_H_
+
+#include "talk/base/rollingaccumulator.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+
+// The purpose of BandwidthSmoother is to smooth out bandwidth
+// estimations so that 'trstate' messages can be triggered when we
+// are "sure" there is sufficient bandwidth.  To avoid frequent fluctuations,
+// we take a slightly pessimistic view of our bandwidth.  We only increase
+// our estimation when we have sampled bandwidth measurements of values
+// at least as large as the current estimation * percent_increase
+// for at least time_between_increase time.  If a sampled bandwidth
+// is less than our current estimation we immediately decrease our estimation
+// to that sampled value.
+class BandwidthSmoother {
+ public:
+  BandwidthSmoother(int initial_bandwidth_guess,
+                    uint32 time_between_increase,
+                    double percent_increase,
+                    size_t samples_count_to_average);
+
+  // Samples a new bandwidth measurement.
+  // bandwidth is expected to be non-negative.
+  // returns true if the bandwidth estimation changed
+  bool Sample(uint32 sample_time, int bandwidth);
+
+  int get_bandwidth_estimation() const {
+    return bandwidth_estimation_;
+  }
+
+ private:
+  uint32 time_between_increase_;
+  double percent_increase_;
+  uint32 time_at_last_change_;
+  int bandwidth_estimation_;
+  RollingAccumulator<int> accumulator_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_BANDWIDTHSMOOTHER_H_
diff --git a/talk/base/bandwidthsmoother_unittest.cc b/talk/base/bandwidthsmoother_unittest.cc
new file mode 100644
index 0000000..1eb79bd
--- /dev/null
+++ b/talk/base/bandwidthsmoother_unittest.cc
@@ -0,0 +1,106 @@
+/*
+ * 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 <limits.h>
+
+#include "talk/base/bandwidthsmoother.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+class BandwidthSmootherTest : public testing::Test {
+ public:
+};
+
+static const int kTimeBetweenIncrease = 10;
+static const double kPercentIncrease = 1.1;
+static const size_t kSamplesCountToAverage = 1;
+
+TEST_F(BandwidthSmootherTest, TestSampleIncrease) {
+  BandwidthSmoother mon(1000,  // initial_bandwidth_guess
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        kSamplesCountToAverage);
+
+  int bandwidth_sample = 1000;
+  EXPECT_EQ(bandwidth_sample, mon.get_bandwidth_estimation());
+  bandwidth_sample =
+      static_cast<int>(bandwidth_sample * kPercentIncrease);
+  EXPECT_FALSE(mon.Sample(9, bandwidth_sample));
+  EXPECT_TRUE(mon.Sample(10, bandwidth_sample));
+  EXPECT_EQ(bandwidth_sample, mon.get_bandwidth_estimation());
+  int next_expected_est =
+      static_cast<int>(bandwidth_sample * kPercentIncrease);
+  bandwidth_sample *= 2;
+  EXPECT_TRUE(mon.Sample(20, bandwidth_sample));
+  EXPECT_EQ(next_expected_est, mon.get_bandwidth_estimation());
+}
+
+TEST_F(BandwidthSmootherTest, TestSampleIncreaseFromZero) {
+  BandwidthSmoother mon(0,  // initial_bandwidth_guess
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        kSamplesCountToAverage);
+
+  int bandwidth_sample = 1000;
+  EXPECT_FALSE(mon.Sample(9, bandwidth_sample));
+  EXPECT_TRUE(mon.Sample(10, bandwidth_sample));
+  EXPECT_EQ(bandwidth_sample/2, mon.get_bandwidth_estimation());
+}
+
+TEST_F(BandwidthSmootherTest, TestSampleDecrease) {
+  BandwidthSmoother mon(1000,  // initial_bandwidth_guess
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        kSamplesCountToAverage);
+
+  int bandwidth_sample = 999;
+  EXPECT_TRUE(mon.Sample(1, bandwidth_sample));
+  EXPECT_EQ(bandwidth_sample, mon.get_bandwidth_estimation());
+}
+
+TEST_F(BandwidthSmootherTest, TestSampleRollover) {
+  const int initial_bandwidth_guess = 2000000000;  // > INT_MAX/1.1
+  BandwidthSmoother mon(initial_bandwidth_guess,
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        kSamplesCountToAverage);
+
+  EXPECT_FALSE(mon.Sample(10, INT_MAX));
+  EXPECT_EQ(initial_bandwidth_guess, mon.get_bandwidth_estimation());
+}
+
+TEST_F(BandwidthSmootherTest, TestSampleNegative) {
+  BandwidthSmoother mon(1000,  // initial_bandwidth_guess
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        kSamplesCountToAverage);
+
+  EXPECT_FALSE(mon.Sample(10, -1));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/base64_unittest.cc b/talk/base/base64_unittest.cc
new file mode 100644
index 0000000..644a5e0
--- /dev/null
+++ b/talk/base/base64_unittest.cc
@@ -0,0 +1,1004 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/common.h"
+#include "talk/base/base64.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/stream.h"
+
+#include "talk/base/testbase64.h"
+
+using namespace std;
+using namespace talk_base;
+
+static struct {
+  size_t plain_length;
+  const char* plaintext;
+  const char* cyphertext;
+} base64_tests[] = {
+
+  // Basic bit patterns;
+  // values obtained with "echo -n '...' | uuencode -m test"
+
+  { 1, "\000", "AA==" },
+  { 1, "\001", "AQ==" },
+  { 1, "\002", "Ag==" },
+  { 1, "\004", "BA==" },
+  { 1, "\010", "CA==" },
+  { 1, "\020", "EA==" },
+  { 1, "\040", "IA==" },
+  { 1, "\100", "QA==" },
+  { 1, "\200", "gA==" },
+
+  { 1, "\377", "/w==" },
+  { 1, "\376", "/g==" },
+  { 1, "\375", "/Q==" },
+  { 1, "\373", "+w==" },
+  { 1, "\367", "9w==" },
+  { 1, "\357", "7w==" },
+  { 1, "\337", "3w==" },
+  { 1, "\277", "vw==" },
+  { 1, "\177", "fw==" },
+  { 2, "\000\000", "AAA=" },
+  { 2, "\000\001", "AAE=" },
+  { 2, "\000\002", "AAI=" },
+  { 2, "\000\004", "AAQ=" },
+  { 2, "\000\010", "AAg=" },
+  { 2, "\000\020", "ABA=" },
+  { 2, "\000\040", "ACA=" },
+  { 2, "\000\100", "AEA=" },
+  { 2, "\000\200", "AIA=" },
+  { 2, "\001\000", "AQA=" },
+  { 2, "\002\000", "AgA=" },
+  { 2, "\004\000", "BAA=" },
+  { 2, "\010\000", "CAA=" },
+  { 2, "\020\000", "EAA=" },
+  { 2, "\040\000", "IAA=" },
+  { 2, "\100\000", "QAA=" },
+  { 2, "\200\000", "gAA=" },
+
+  { 2, "\377\377", "//8=" },
+  { 2, "\377\376", "//4=" },
+  { 2, "\377\375", "//0=" },
+  { 2, "\377\373", "//s=" },
+  { 2, "\377\367", "//c=" },
+  { 2, "\377\357", "/+8=" },
+  { 2, "\377\337", "/98=" },
+  { 2, "\377\277", "/78=" },
+  { 2, "\377\177", "/38=" },
+  { 2, "\376\377", "/v8=" },
+  { 2, "\375\377", "/f8=" },
+  { 2, "\373\377", "+/8=" },
+  { 2, "\367\377", "9/8=" },
+  { 2, "\357\377", "7/8=" },
+  { 2, "\337\377", "3/8=" },
+  { 2, "\277\377", "v/8=" },
+  { 2, "\177\377", "f/8=" },
+
+  { 3, "\000\000\000", "AAAA" },
+  { 3, "\000\000\001", "AAAB" },
+  { 3, "\000\000\002", "AAAC" },
+  { 3, "\000\000\004", "AAAE" },
+  { 3, "\000\000\010", "AAAI" },
+  { 3, "\000\000\020", "AAAQ" },
+  { 3, "\000\000\040", "AAAg" },
+  { 3, "\000\000\100", "AABA" },
+  { 3, "\000\000\200", "AACA" },
+  { 3, "\000\001\000", "AAEA" },
+  { 3, "\000\002\000", "AAIA" },
+  { 3, "\000\004\000", "AAQA" },
+  { 3, "\000\010\000", "AAgA" },
+  { 3, "\000\020\000", "ABAA" },
+  { 3, "\000\040\000", "ACAA" },
+  { 3, "\000\100\000", "AEAA" },
+  { 3, "\000\200\000", "AIAA" },
+  { 3, "\001\000\000", "AQAA" },
+  { 3, "\002\000\000", "AgAA" },
+  { 3, "\004\000\000", "BAAA" },
+  { 3, "\010\000\000", "CAAA" },
+  { 3, "\020\000\000", "EAAA" },
+  { 3, "\040\000\000", "IAAA" },
+  { 3, "\100\000\000", "QAAA" },
+  { 3, "\200\000\000", "gAAA" },
+
+  { 3, "\377\377\377", "////" },
+  { 3, "\377\377\376", "///+" },
+  { 3, "\377\377\375", "///9" },
+  { 3, "\377\377\373", "///7" },
+  { 3, "\377\377\367", "///3" },
+  { 3, "\377\377\357", "///v" },
+  { 3, "\377\377\337", "///f" },
+  { 3, "\377\377\277", "//+/" },
+  { 3, "\377\377\177", "//9/" },
+  { 3, "\377\376\377", "//7/" },
+  { 3, "\377\375\377", "//3/" },
+  { 3, "\377\373\377", "//v/" },
+  { 3, "\377\367\377", "//f/" },
+  { 3, "\377\357\377", "/+//" },
+  { 3, "\377\337\377", "/9//" },
+  { 3, "\377\277\377", "/7//" },
+  { 3, "\377\177\377", "/3//" },
+  { 3, "\376\377\377", "/v//" },
+  { 3, "\375\377\377", "/f//" },
+  { 3, "\373\377\377", "+///" },
+  { 3, "\367\377\377", "9///" },
+  { 3, "\357\377\377", "7///" },
+  { 3, "\337\377\377", "3///" },
+  { 3, "\277\377\377", "v///" },
+  { 3, "\177\377\377", "f///" },
+
+  // Random numbers: values obtained with
+  //
+  //  #! /bin/bash
+  //  dd bs=$1 count=1 if=/dev/random of=/tmp/bar.random
+  //  od -N $1 -t o1 /tmp/bar.random
+  //  uuencode -m test < /tmp/bar.random
+  //
+  // where $1 is the number of bytes (2, 3)
+
+  { 2, "\243\361", "o/E=" },
+  { 2, "\024\167", "FHc=" },
+  { 2, "\313\252", "y6o=" },
+  { 2, "\046\041", "JiE=" },
+  { 2, "\145\236", "ZZ4=" },
+  { 2, "\254\325", "rNU=" },
+  { 2, "\061\330", "Mdg=" },
+  { 2, "\245\032", "pRo=" },
+  { 2, "\006\000", "BgA=" },
+  { 2, "\375\131", "/Vk=" },
+  { 2, "\303\210", "w4g=" },
+  { 2, "\040\037", "IB8=" },
+  { 2, "\261\372", "sfo=" },
+  { 2, "\335\014", "3Qw=" },
+  { 2, "\233\217", "m48=" },
+  { 2, "\373\056", "+y4=" },
+  { 2, "\247\232", "p5o=" },
+  { 2, "\107\053", "Rys=" },
+  { 2, "\204\077", "hD8=" },
+  { 2, "\276\211", "vok=" },
+  { 2, "\313\110", "y0g=" },
+  { 2, "\363\376", "8/4=" },
+  { 2, "\251\234", "qZw=" },
+  { 2, "\103\262", "Q7I=" },
+  { 2, "\142\312", "Yso=" },
+  { 2, "\067\211", "N4k=" },
+  { 2, "\220\001", "kAE=" },
+  { 2, "\152\240", "aqA=" },
+  { 2, "\367\061", "9zE=" },
+  { 2, "\133\255", "W60=" },
+  { 2, "\176\035", "fh0=" },
+  { 2, "\032\231", "Gpk=" },
+
+  { 3, "\013\007\144", "Cwdk" },
+  { 3, "\030\112\106", "GEpG" },
+  { 3, "\047\325\046", "J9Um" },
+  { 3, "\310\160\022", "yHAS" },
+  { 3, "\131\100\237", "WUCf" },
+  { 3, "\064\342\134", "NOJc" },
+  { 3, "\010\177\004", "CH8E" },
+  { 3, "\345\147\205", "5WeF" },
+  { 3, "\300\343\360", "wOPw" },
+  { 3, "\061\240\201", "MaCB" },
+  { 3, "\225\333\044", "ldsk" },
+  { 3, "\215\137\352", "jV/q" },
+  { 3, "\371\147\160", "+Wdw" },
+  { 3, "\030\320\051", "GNAp" },
+  { 3, "\044\174\241", "JHyh" },
+  { 3, "\260\127\037", "sFcf" },
+  { 3, "\111\045\033", "SSUb" },
+  { 3, "\202\114\107", "gkxH" },
+  { 3, "\057\371\042", "L/ki" },
+  { 3, "\223\247\244", "k6ek" },
+  { 3, "\047\216\144", "J45k" },
+  { 3, "\203\070\327", "gzjX" },
+  { 3, "\247\140\072", "p2A6" },
+  { 3, "\124\115\116", "VE1O" },
+  { 3, "\157\162\050", "b3Io" },
+  { 3, "\357\223\004", "75ME" },
+  { 3, "\052\117\156", "Kk9u" },
+  { 3, "\347\154\000", "52wA" },
+  { 3, "\303\012\142", "wwpi" },
+  { 3, "\060\035\362", "MB3y" },
+  { 3, "\130\226\361", "WJbx" },
+  { 3, "\173\013\071", "ews5" },
+  { 3, "\336\004\027", "3gQX" },
+  { 3, "\357\366\234", "7/ac" },
+  { 3, "\353\304\111", "68RJ" },
+  { 3, "\024\264\131", "FLRZ" },
+  { 3, "\075\114\251", "PUyp" },
+  { 3, "\315\031\225", "zRmV" },
+  { 3, "\154\201\276", "bIG+" },
+  { 3, "\200\066\072", "gDY6" },
+  { 3, "\142\350\267", "Yui3" },
+  { 3, "\033\000\166", "GwB2" },
+  { 3, "\210\055\077", "iC0/" },
+  { 3, "\341\037\124", "4R9U" },
+  { 3, "\161\103\152", "cUNq" },
+  { 3, "\270\142\131", "uGJZ" },
+  { 3, "\337\076\074", "3z48" },
+  { 3, "\375\106\362", "/Uby" },
+  { 3, "\227\301\127", "l8FX" },
+  { 3, "\340\002\234", "4AKc" },
+  { 3, "\121\064\033", "UTQb" },
+  { 3, "\157\134\143", "b1xj" },
+  { 3, "\247\055\327", "py3X" },
+  { 3, "\340\142\005", "4GIF" },
+  { 3, "\060\260\143", "MLBj" },
+  { 3, "\075\203\170", "PYN4" },
+  { 3, "\143\160\016", "Y3AO" },
+  { 3, "\313\013\063", "ywsz" },
+  { 3, "\174\236\135", "fJ5d" },
+  { 3, "\103\047\026", "QycW" },
+  { 3, "\365\005\343", "9QXj" },
+  { 3, "\271\160\223", "uXCT" },
+  { 3, "\362\255\172", "8q16" },
+  { 3, "\113\012\015", "SwoN" },
+
+  // various lengths, generated by this python script:
+  //
+  // from string import lowercase as lc
+  // for i in range(27):
+  //   print '{ %2d, "%s",%s "%s" },' % (i, lc[:i], ' ' * (26-i),
+  //                                     lc[:i].encode('base64').strip())
+
+  {  0, "abcdefghijklmnopqrstuvwxyz", "" },
+  {  1, "abcdefghijklmnopqrstuvwxyz", "YQ==" },
+  {  2, "abcdefghijklmnopqrstuvwxyz", "YWI=" },
+  {  3, "abcdefghijklmnopqrstuvwxyz", "YWJj" },
+  {  4, "abcdefghijklmnopqrstuvwxyz", "YWJjZA==" },
+  {  5, "abcdefghijklmnopqrstuvwxyz", "YWJjZGU=" },
+  {  6, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVm" },
+  {  7, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZw==" },
+  {  8, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2g=" },
+  {  9, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hp" },
+  { 10, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpag==" },
+  { 11, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpams=" },
+  { 12, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamts" },
+  { 13, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbQ==" },
+  { 14, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW4=" },
+  { 15, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5v" },
+  { 16, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcA==" },
+  { 17, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHE=" },
+  { 18, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFy" },
+  { 19, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFycw==" },
+  { 20, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3Q=" },
+  { 21, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1" },
+  { 22, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dg==" },
+  { 23, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnc=" },
+  { 24, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4" },
+  { 25, "abcdefghijklmnopqrstuvwxy",  "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eQ==" },
+  { 26, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=" },
+};
+#if 0
+static struct {
+  const char* plaintext;
+  const char* cyphertext;
+} base64_strings[] = {
+
+  // The first few Google quotes
+  // Cyphertext created with "uuencode - GNU sharutils 4.2.1"
+  {
+    "Everyone!  We're teetering on the brink of disaster."
+    " - Sergey Brin, 6/24/99, regarding the company's state "
+    "after the unleashing of Netscape/Google search",
+
+    "RXZlcnlvbmUhICBXZSdyZSB0ZWV0ZXJpbmcgb24gdGhlIGJyaW5rIG9mIGRp"
+    "c2FzdGVyLiAtIFNlcmdleSBCcmluLCA2LzI0Lzk5LCByZWdhcmRpbmcgdGhl"
+    "IGNvbXBhbnkncyBzdGF0ZSBhZnRlciB0aGUgdW5sZWFzaGluZyBvZiBOZXRz"
+    "Y2FwZS9Hb29nbGUgc2VhcmNo" },
+
+  {
+    "I'm not sure why we're still alive, but we seem to be."
+    " - Larry Page, 6/24/99, while hiding in the kitchenette "
+    "during the Netscape traffic overflow",
+
+    "SSdtIG5vdCBzdXJlIHdoeSB3ZSdyZSBzdGlsbCBhbGl2ZSwgYnV0IHdlIHNl"
+    "ZW0gdG8gYmUuIC0gTGFycnkgUGFnZSwgNi8yNC85OSwgd2hpbGUgaGlkaW5n"
+    "IGluIHRoZSBraXRjaGVuZXR0ZSBkdXJpbmcgdGhlIE5ldHNjYXBlIHRyYWZm"
+    "aWMgb3ZlcmZsb3c" },
+
+  {
+    "I think kids want porn."
+    " - Sergey Brin, 6/99, on why Google shouldn't prioritize a "
+    "filtered search for children and families",
+
+    "SSB0aGluayBraWRzIHdhbnQgcG9ybi4gLSBTZXJnZXkgQnJpbiwgNi85OSwg"
+    "b24gd2h5IEdvb2dsZSBzaG91bGRuJ3QgcHJpb3JpdGl6ZSBhIGZpbHRlcmVk"
+    "IHNlYXJjaCBmb3IgY2hpbGRyZW4gYW5kIGZhbWlsaWVz" },
+};
+#endif
+// Compare bytes 0..len-1 of x and y.  If not equal, abort with verbose error
+// message showing position and numeric value that differed.
+// Handles embedded nulls just like any other byte.
+// Only added because string.compare() in gcc-3.3.3 seems to misbehave with
+// embedded nulls.
+// TODO: switch back to string.compare() if/when gcc is fixed
+#define EXPECT_EQ_ARRAY(len, x, y, msg)                      \
+  for (size_t j = 0; j < len; ++j) {                           \
+    if (x[j] != y[j]) {                                     \
+        LOG(LS_ERROR) << "" # x << " != " # y                  \
+                   << " byte " << j << " msg: " << msg;     \
+      }                                                     \
+    }
+
+size_t Base64Escape(const unsigned char *src, size_t szsrc, char *dest,
+                    size_t szdest) {
+  std::string escaped;
+  Base64::EncodeFromArray((const char *)src, szsrc, &escaped);
+  memcpy(dest, escaped.data(), min(escaped.size(), szdest));
+  return escaped.size();
+}
+
+size_t Base64Unescape(const char *src, size_t szsrc, char *dest,
+                      size_t szdest) {
+  std::string unescaped;
+  EXPECT_TRUE(Base64::DecodeFromArray(src, szsrc, Base64::DO_LAX, &unescaped,
+                                      NULL));
+  memcpy(dest, unescaped.data(), min(unescaped.size(), szdest));
+  return unescaped.size();
+}
+
+size_t Base64Unescape(const char *src, size_t szsrc, string *s) {
+  EXPECT_TRUE(Base64::DecodeFromArray(src, szsrc, Base64::DO_LAX, s, NULL));
+  return s->size();
+}
+
+TEST(Base64, EncodeDecodeBattery) {
+  LOG(LS_VERBOSE) << "Testing base-64";
+
+  size_t i;
+
+  // Check the short strings; this tests the math (and boundaries)
+  for( i = 0; i < sizeof(base64_tests) / sizeof(base64_tests[0]); ++i ) {
+    char encode_buffer[100];
+    size_t encode_length;
+    char decode_buffer[100];
+    size_t decode_length;
+    size_t cypher_length;
+
+    LOG(LS_VERBOSE) << "B64: " << base64_tests[i].cyphertext;
+
+    const unsigned char* unsigned_plaintext =
+      reinterpret_cast<const unsigned char*>(base64_tests[i].plaintext);
+
+    cypher_length = strlen(base64_tests[i].cyphertext);
+
+    // The basic escape function:
+    memset(encode_buffer, 0, sizeof(encode_buffer));
+    encode_length = Base64Escape(unsigned_plaintext,
+                                 base64_tests[i].plain_length,
+                                 encode_buffer,
+                                 sizeof(encode_buffer));
+    //    Is it of the expected length?
+    EXPECT_EQ(encode_length, cypher_length);
+
+    //    Is it the expected encoded value?
+    EXPECT_STREQ(encode_buffer, base64_tests[i].cyphertext);
+
+    // If we encode it into a buffer of exactly the right length...
+    memset(encode_buffer, 0, sizeof(encode_buffer));
+    encode_length = Base64Escape(unsigned_plaintext,
+                                 base64_tests[i].plain_length,
+                                 encode_buffer,
+                                 cypher_length);
+    //    Is it still of the expected length?
+    EXPECT_EQ(encode_length, cypher_length);
+
+    //    And is the value still correct?  (i.e., not losing the last byte)
+    EXPECT_STREQ(encode_buffer, base64_tests[i].cyphertext);
+
+    // If we decode it back:
+    memset(decode_buffer, 0, sizeof(decode_buffer));
+    decode_length = Base64Unescape(encode_buffer,
+                                   cypher_length,
+                                   decode_buffer,
+                                   sizeof(decode_buffer));
+
+    //    Is it of the expected length?
+    EXPECT_EQ(decode_length, base64_tests[i].plain_length);
+
+    //    Is it the expected decoded value?
+    EXPECT_EQ(0,  memcmp(decode_buffer, base64_tests[i].plaintext, decode_length));
+
+    // Our decoder treats the padding '=' characters at the end as
+    // optional.  If encode_buffer has any, run some additional
+    // tests that fiddle with them.
+    char* first_equals = strchr(encode_buffer, '=');
+    if (first_equals) {
+      // How many equals signs does the string start with?
+      int equals = (*(first_equals+1) == '=') ? 2 : 1;
+
+      // Try chopping off the equals sign(s) entirely.  The decoder
+      // should still be okay with this.
+      string decoded2("this junk should also be ignored");
+      *first_equals = '\0';
+      EXPECT_NE(0U, Base64Unescape(encode_buffer, first_equals-encode_buffer,
+                           &decoded2));
+      EXPECT_EQ(decoded2.size(), base64_tests[i].plain_length);
+      EXPECT_EQ_ARRAY(decoded2.size(), decoded2.data(), base64_tests[i].plaintext, i);
+
+      int len;
+
+      // try putting some extra stuff after the equals signs, or in between them
+      if (equals == 2) {
+        sprintfn(first_equals, 6, " = = ");
+        len = first_equals - encode_buffer + 5;
+      } else {
+        sprintfn(first_equals, 6, " = ");
+        len = first_equals - encode_buffer + 3;
+      }
+      decoded2.assign("this junk should be ignored");
+      EXPECT_NE(0U, Base64Unescape(encode_buffer, len, &decoded2));
+      EXPECT_EQ(decoded2.size(), base64_tests[i].plain_length);
+      EXPECT_EQ_ARRAY(decoded2.size(), decoded2, base64_tests[i].plaintext, i);
+    }
+  }
+}
+
+// here's a weird case: a giant base64 encoded stream which broke our base64
+// decoding.  Let's test it explicitly.
+const char SpecificTest[] =
+  "/9j/4AAQSkZJRgABAgEASABIAAD/4Q0HRXhpZgAATU0AKgAAAAgADAEOAAIAAAAgAAAAngEPAAI\n"
+  "AAAAFAAAAvgEQAAIAAAAJAAAAwwESAAMAAAABAAEAAAEaAAUAAAABAAAAzAEbAAUAAAABAAAA1A\n"
+  "EoAAMAAAABAAIAAAExAAIAAAAUAAAA3AEyAAIAAAAUAAAA8AE8AAIAAAAQAAABBAITAAMAAAABA\n"
+  "AIAAIdpAAQAAAABAAABFAAAAsQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAFNPTlkA\n"
+  "RFNDLVAyMDAAAAAASAAAAAEAAABIAAAAAUFkb2JlIFBob3Rvc2hvcCA3LjAAMjAwNzowMTozMCA\n"
+  "yMzoxMDowNABNYWMgT1MgWCAxMC40LjgAAByCmgAFAAAAAQAAAmqCnQAFAAAAAQAAAnKIIgADAA\n"
+  "AAAQACAACIJwADAAAAAQBkAACQAAAHAAAABDAyMjCQAwACAAAAFAAAAnqQBAACAAAAFAAAAo6RA\n"
+  "QAHAAAABAECAwCRAgAFAAAAAQAAAqKSBAAKAAAAAQAAAqqSBQAFAAAAAQAAArKSBwADAAAAAQAF\n"
+  "AACSCAADAAAAAQAAAACSCQADAAAAAQAPAACSCgAFAAAAAQAAArqgAAAHAAAABDAxMDCgAQADAAA\n"
+  "AAf//AACgAgAEAAAAAQAAAGSgAwAEAAAAAQAAAGSjAAAHAAAAAQMAAACjAQAHAAAAAQEAAACkAQ\n"
+  "ADAAAAAQAAAACkAgADAAAAAQAAAACkAwADAAAAAQAAAACkBgADAAAAAQAAAACkCAADAAAAAQAAA\n"
+  "ACkCQADAAAAAQAAAACkCgADAAAAAQAAAAAAAAAAAAAACgAAAZAAAAAcAAAACjIwMDc6MDE6MjAg\n"
+  "MjM6MDU6NTIAMjAwNzowMToyMCAyMzowNTo1MgAAAAAIAAAAAQAAAAAAAAAKAAAAMAAAABAAAAB\n"
+  "PAAAACgAAAAYBAwADAAAAAQAGAAABGgAFAAAAAQAAAxIBGwAFAAAAAQAAAxoBKAADAAAAAQACAA\n"
+  "ACAQAEAAAAAQAAAyICAgAEAAAAAQAACd0AAAAAAAAASAAAAAEAAABIAAAAAf/Y/+AAEEpGSUYAA\n"
+  "QIBAEgASAAA/+0ADEFkb2JlX0NNAAL/7gAOQWRvYmUAZIAAAAAB/9sAhAAMCAgICQgMCQkMEQsK\n"
+  "CxEVDwwMDxUYExMVExMYEQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAQ0LCw0\n"
+  "ODRAODhAUDg4OFBQODg4OFBEMDAwMDBERDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA\n"
+  "wMDAz/wAARCABkAGQDASIAAhEBAxEB/90ABAAH/8QBPwAAAQUBAQEBAQEAAAAAAAAAAwABAgQFB\n"
+  "gcICQoLAQABBQEBAQEBAQAAAAAAAAABAAIDBAUGBwgJCgsQAAEEAQMCBAIFBwYIBQMMMwEAAhED\n"
+  "BCESMQVBUWETInGBMgYUkaGxQiMkFVLBYjM0coLRQwclklPw4fFjczUWorKDJkSTVGRFwqN0Nhf\n"
+  "SVeJl8rOEw9N14/NGJ5SkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9xEAAg\n"
+  "IBAgQEAwQFBgcHBgU1AQACEQMhMRIEQVFhcSITBTKBkRShsUIjwVLR8DMkYuFygpJDUxVjczTxJ\n"
+  "QYWorKDByY1wtJEk1SjF2RFVTZ0ZeLys4TD03Xj80aUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm\n"
+  "9ic3R1dnd4eXp7fH/9oADAMBAAIRAxEAPwDy7bKNTUXNLz9EaJPDWMjxH4ozhtpYwaACT8ShaaW\n"
+  "bW0uEc9/JFfjj0Q4Hk/PRDxwX7y47W9z/AN9Cv4+O3ILK2DcRqT2CaSvEbcl1Jbz37KG1dBldLo\n"
+  "qaS4l9xGjG9v6yoDAdYIaIjUk+AREgo4y5sapirb8Yl0NHHdKvBNm4yA1o5Pc+SPEFvCWqB3HZF\n"
+  "Hj2SbWQ/afGFP0bHP8ATY0uc4w1o1JPkkimGiS2KvqlnmBkOZQTyydzgPMM9v8A0lp4v1Nx9gF1\n"
+  "tpdqJaGtH/S3I0i3lISXW/8AMqnd/O2bfg2eUkqVYf/Q8zuncO4Bj7lZ+n7f5Mj5KsJcY8NUZ4d\n"
+  "uEDVo1HkeU0rg3Om4H2rabCWUN7DQuK1n5FWKW4uCwG92gDRJBS6exhxmMboQI+Cv4WFTQ42Bs2\n"
+  "fvnkkqEmy2YxoMMbpVzaz6jt+RbpHZs8lzkHqrasKkYOKP0jgDfZ4N/wDM1tNrcWfSPmRyq9uNV\n"
+  "DnFg2s97i7UkjxKVrq0eVz3spZsja+ASDzwsh9jnOk/JFzb3XZD3v1c4yT8UACTCniKDUnKz5Nj\n"
+  "G33XV1DV73BrT8dF23SejV4zg9g33cOsPb+SxVvqv9ViwNy8vS0iWs/daf8A0Y5dpTi1sADGxCR\n"
+  "K1o0YBEmInlXWYbDBcDLdPJXa8f71Yrx2jnUoAqLnfZK5hJaW2vdwEk5a/wD/0fN6Ia/e76IiVf\n"
+  "xavUL7CPpnT4LNbYXAVjuQt/AqDmNYO/Kjnoy4hr5J8SwMhrRMaeSvbsxrfUazcOw4UX0Cisem2\n"
+  "SBoD4+Kz8nC6llbSLCRrubJA8kwUWbUDa29X1PMa7aQWjuDC0MXMdbDbhI7eazBiUfZ6GOYRe1s\n"
+  "WvGgJ8Vbw2+m4Bx9s6JpNHuuGo1FF53r/SHYua61gLse0lzXeBP5rkvqx0o5vVWz7WY49QkiQSP\n"
+  "oN/tLoevW/ogxv0HA7tJ0AnhT+pdDGYVl/wCdcTPkGn2NU0JWNWvlgAbHV6fEqdu2gR/r2WlWwt\n"
+  "AA5VXAEsLXTqJafArQY5rRr9LiPBJiZsZCI1pJjxCi0j4oncSICSkWwzwkjeaSch//0vO7sP7Lm\n"
+  "enO9ogtd5FbPT3Q5pCpZVc4ld3Lmn3O8j9EI2BYdunKjOobMQIyI+rusc2wx4d0eutwGnHh/uQc\n"
+  "Ha7ladj6mVANGvcqOgz0Go7HJ12/GEHcwvB/dPY6ImbbaMaASGuIBjkN7qofs9Ubg9g7OI9p/t/\n"
+  "RTSmhTHr0v6eSz6UgCPP2/wAVu9Ex2V49dVY2iACB4BZeVXQ/AJ3gzGnnOi2+kACpru8flUsNmt\n"
+  "zHRf6xfWCnoeAfTh2ZaQKazx/Ke7+QxcKz61fWA2uuObaC4zGhaPJrXBL64ZFmR124O09ENraPK\n"
+  "N3/AH5GqxIrZVUyp2K2vfdkENsDnxuex9m4Ox9n82xSgNd9D+p/XR1npgseR9ppOy4Dx/NfH/CL\n"
+  "oQJGunmvMv8AFq3KHVcq3HkYQbD2nuSf0I/rMavSg6TLjLigQhJ7Z58v9QkmlsTOqSCn/9PzL7R\n"
+  "d6Qq3n0wZ2zotXpT9xLfFYvkr/S7jXeB8E0jRkhKpC3q8LcJ/kmCrTnkuAPCq4do9Q/ytVbuAeY\n"
+  "Gg5lQybQK+82GBqEQUA1kOHPYf3LLsoyN36G5w8iUfHxepbXE2l0cApALgLHzBq9UxhTXU5hMC1\n"
+  "ktnSCup6S4Ctk+C5XqVGcaHPfuiuHkeTTuWz0+9zaKiH6CC0/yXBSQ2a/MxojV57634rq+v2PLY\n"
+  "be1r2nsYG13/AFKxbfCBMcr0brGAzrGEwCG31ncx0SfBzf7S4+zoHUWWsJq3hz9oLfcBH77R9H+\n"
+  "0pA13u/qPgDp/Q6ri39JlfpXkDx+h/msWn1L6wdO6bSbcrIbU2Q0xLnSe21kuVejJspbVS5+4bd\n"
+  "ocBAkD/orG+tP1ar67Wy7GtZTm1SCXfRsb+a18fRe38x6SG3/44H1Z3f0y2I+l6DoSXD/8xPrDs\n"
+  "3enVu3bdnqN3R+//USSVo//1PLohhce+gRWS0Nsby3lRgFkKxQyW7SgUh3em5Tbq2uB9wWw1wey\n"
+  "J1XGV2XYdm5k7e4WzidXY9oMwo5RZ4T6Hd1ixwfp96PWbAJBVTHzK7O6Ky5oJB1HZMqmUEFlkGy\n"
+  "xpa4zI1Hkq31dy7bMN9BAc3HeWAnnbyxEycmuup1jiAGglZ31PyrmZ9tQg1WtNj54EHR3/S2qTH\n"
+  "1Yc5GgD1FFtzPdWGkd2AyflogZmRmsz6PSrbXbdo+txOrP337f3fzVo15DK2uyrTtqpBOnBKx6b\n"
+  "7MjJsz7tHWOAYP3WD6LU6cqGjFCNl1MmvLcxv6YtDTLSAqP27LrdtYHXFnJZI+Tp3MWg68OpDPv\n"
+  "UMUM2lkQBoouKQ6swjE9Nml+1sz1PW+z6xt27zuj+skrX2ZvqR5z8kkuOfdPt43/1fMm/grFG6f\n"
+  "Lss9JA7JG7tnZs/SfJUrfS3foJ9TvHCopJsV8nWx/t24bJn8Fo/5TjWJXMJIS+i+G36TsZ/7Q9P\n"
+  "8ATfzfeOFofVSZv2/zvt+O3X/v65dJPjt/BiyfN1/wn0zre79nVej/ADG8ep4x2/6Srjd6TdviF\n"
+  "52ko8m6/Ht9X1KnftEo+POwxzK8mSTF46vrH6T1/OEl5Okkl//Z/+0uHFBob3Rvc2hvcCAzLjAA\n"
+  "OEJJTQQEAAAAAAArHAIAAAIAAhwCeAAfICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAA\n"
+  "4QklNBCUAAAAAABD7Caa9B0wqNp2P4sxXqayFOEJJTQPqAAAAAB2wPD94bWwgdmVyc2lvbj0iMS\n"
+  "4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUgQ\n"
+  "29tcHV0ZXIvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Q\n"
+  "cm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk\n"
+  "+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1Ib3Jpem9udGFsUmVzPC9rZXk+Cgk8ZGljdD\n"
+  "4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY3JlYXRvcjwva2V5PgoJCTxzdHJpbmc+Y\n"
+  "29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCTxrZXk+Y29tLmFwcGxlLnByaW50\n"
+  "LnRpY2tldC5pdGVtQXJyYXk8L2tleT4KCQk8YXJyYXk+CgkJCTxkaWN0PgoJCQkJPGtleT5jb20\n"
+  "uYXBwbGUucHJpbnQuUGFnZUZvcm1hdC5QTUhvcml6b250YWxSZXM8L2tleT4KCQkJCTxyZWFsPj\n"
+  "cyPC9yZWFsPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDwva2V5PgoJC\n"
+  "QkJPHN0cmluZz5jb20uYXBwbGUucHJpbnRpbmdtYW5hZ2VyPC9zdHJpbmc+CgkJCQk8a2V5PmNv\n"
+  "bS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQkJPGRhdGU+MjAwNy0wMS0zMFQ\n"
+  "yMjowODo0MVo8L2RhdGU+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuc3RhdGVGbG\n"
+  "FnPC9rZXk+CgkJCQk8aW50ZWdlcj4wPC9pbnRlZ2VyPgoJCQk8L2RpY3Q+CgkJPC9hcnJheT4KC\n"
+  "TwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1PcmllbnRhdGlvbjwv\n"
+  "a2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4\n"
+  "KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQk8a2V5PmNvbS\n"
+  "5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFycmF5PgoJCQk8ZGljdD4KC\n"
+  "QkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1PcmllbnRhdGlvbjwva2V5PgoJ\n"
+  "CQkJPGludGVnZXI+MTwvaW50ZWdlcj4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5\n"
+  "jbGllbnQ8L2tleT4KCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW\n"
+  "5nPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0Lm1vZERhdGU8L2tleT4KCQkJCTxkY\n"
+  "XRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQu\n"
+  "dGlja2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJPGludGVnZXI+MDwvaW50ZWdlcj4KCQkJPC9kaWN\n"
+  "0PgoJCTwvYXJyYXk+Cgk8L2RpY3Q+Cgk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0Ll\n"
+  "BNU2NhbGluZzwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZ\n"
+  "WF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4K\n"
+  "CQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFycmF5Pgo\n"
+  "JCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1TY2FsaW5nPC\n"
+  "9rZXk+CgkJCQk8cmVhbD4xPC9yZWFsPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0L\n"
+  "mNsaWVudDwva2V5PgoJCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnRpbmdtYW5hZ2VyPC9zdHJp\n"
+  "bmc+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQkJPGR\n"
+  "hdGU+MjAwNy0wMS0zMFQyMjowODo0MVo8L2RhdGU+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC\n"
+  "50aWNrZXQuc3RhdGVGbGFnPC9rZXk+CgkJCQk8aW50ZWdlcj4wPC9pbnRlZ2VyPgoJCQk8L2RpY\n"
+  "3Q+CgkJPC9hcnJheT4KCTwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQu\n"
+  "UE1WZXJ0aWNhbFJlczwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V\n"
+  "0LmNyZWF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cm\n"
+  "luZz4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFyc\n"
+  "mF5PgoJCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1WZXJ0\n"
+  "aWNhbFJlczwva2V5PgoJCQkJPHJlYWw+NzI8L3JlYWw+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcml\n"
+  "udC50aWNrZXQuY2xpZW50PC9rZXk+CgkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbm\n"
+  "FnZXI8L3N0cmluZz4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZ\n"
+  "Xk+CgkJCQk8ZGF0ZT4yMDA3LTAxLTMwVDIyOjA4OjQxWjwvZGF0ZT4KCQkJCTxrZXk+Y29tLmFw\n"
+  "cGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI\n"
+  "+CgkJCTwvZGljdD4KCQk8L2FycmF5PgoJPC9kaWN0PgoJPGtleT5jb20uYXBwbGUucHJpbnQuUG\n"
+  "FnZUZvcm1hdC5QTVZlcnRpY2FsU2NhbGluZzwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwb\n"
+  "GUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGlu\n"
+  "Z21hbmFnZXI8L3N0cmluZz4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF\n"
+  "5PC9rZXk+CgkJPGFycmF5PgoJCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2\n"
+  "VGb3JtYXQuUE1WZXJ0aWNhbFNjYWxpbmc8L2tleT4KCQkJCTxyZWFsPjE8L3JlYWw+CgkJCQk8a\n"
+  "2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY2xpZW50PC9rZXk+CgkJCQk8c3RyaW5nPmNvbS5h\n"
+  "cHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnR\n"
+  "pY2tldC5tb2REYXRlPC9rZXk+CgkJCQk8ZGF0ZT4yMDA3LTAxLTMwVDIyOjA4OjQxWjwvZGF0ZT\n"
+  "4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCTxpb\n"
+  "nRlZ2VyPjA8L2ludGVnZXI+CgkJCTwvZGljdD4KCQk8L2FycmF5PgoJPC9kaWN0PgoJPGtleT5j\n"
+  "b20uYXBwbGUucHJpbnQuc3ViVGlja2V0LnBhcGVyX2luZm9fdGlja2V0PC9rZXk+Cgk8ZGljdD4\n"
+  "KCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0LlBNQWRqdXN0ZWRQYWdlUmVjdDwva2\n"
+  "V5PgoJCTxkaWN0PgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY3JlYXRvcjwva2V5P\n"
+  "goJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJPGtleT5j\n"
+  "b20uYXBwbGUucHJpbnQudGlja2V0Lml0ZW1BcnJheTwva2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGl\n"
+  "jdD4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0LlBNQWRqdXN0ZWRQYWdlUm\n"
+  "VjdDwva2V5PgoJCQkJCTxhcnJheT4KCQkJCQkJPHJlYWw+MC4wPC9yZWFsPgoJCQkJCQk8cmVhb\n"
+  "D4wLjA8L3JlYWw+CgkJCQkJCTxyZWFsPjczNDwvcmVhbD4KCQkJCQkJPHJlYWw+NTc2PC9yZWFs\n"
+  "PgoJCQkJCTwvYXJyYXk+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDw\n"
+  "va2V5PgoJCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCQ\n"
+  "kJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPGRhdGU+M\n"
+  "jAwNy0wMS0zMFQyMjowODo0MVo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlj\n"
+  "a2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI+CgkJCQk8L2RpY3Q\n"
+  "+CgkJCTwvYXJyYXk+CgkJPC9kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYX\n"
+  "QuUE1BZGp1c3RlZFBhcGVyUmVjdDwva2V5PgoJCTxkaWN0PgoJCQk8a2V5PmNvbS5hcHBsZS5wc\n"
+  "mludC50aWNrZXQuY3JlYXRvcjwva2V5PgoJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21h\n"
+  "bmFnZXI8L3N0cmluZz4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0Lml0ZW1BcnJheTw\n"
+  "va2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGljdD4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYW\n"
+  "dlRm9ybWF0LlBNQWRqdXN0ZWRQYXBlclJlY3Q8L2tleT4KCQkJCQk8YXJyYXk+CgkJCQkJCTxyZ\n"
+  "WFsPi0xODwvcmVhbD4KCQkJCQkJPHJlYWw+LTE4PC9yZWFsPgoJCQkJCQk8cmVhbD43NzQ8L3Jl\n"
+  "YWw+CgkJCQkJCTxyZWFsPjU5NDwvcmVhbD4KCQkJCQk8L2FycmF5PgoJCQkJCTxrZXk+Y29tLmF\n"
+  "wcGxlLnByaW50LnRpY2tldC5jbGllbnQ8L2tleT4KCQkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcm\n"
+  "ludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQub\n"
+  "W9kRGF0ZTwva2V5PgoJCQkJCTxkYXRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJ\n"
+  "CTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWd\n"
+  "lcj4wPC9pbnRlZ2VyPgoJCQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5Pm\n"
+  "NvbS5hcHBsZS5wcmludC5QYXBlckluZm8uUE1QYXBlck5hbWU8L2tleT4KCQk8ZGljdD4KCQkJP\n"
+  "GtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQkJPHN0cmluZz5jb20u\n"
+  "YXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5nPgoJCQk8a2V5PmNvbS5hcHBsZS5wcml\n"
+  "udC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJheT4KCQkJCTxkaWN0PgoJCQkJCTxrZX\n"
+  "k+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVBhcGVyTmFtZTwva2V5PgoJCQkJCTxzdHJpb\n"
+  "mc+bmEtbGV0dGVyPC9zdHJpbmc+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNs\n"
+  "aWVudDwva2V5PgoJCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50LnBtLlBvc3RTY3JpcHQ8L3N\n"
+  "0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQ\n"
+  "kJCTxkYXRlPjIwMDMtMDctMDFUMTc6NDk6MzZaPC9kYXRlPgoJCQkJCTxrZXk+Y29tLmFwcGxlL\n"
+  "nByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWdlcj4xPC9pbnRlZ2VyPgoJ\n"
+  "CQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC5\n"
+  "QYXBlckluZm8uUE1VbmFkanVzdGVkUGFnZVJlY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5jb2\n"
+  "0uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQkJPHN0cmluZz5jb20uYXBwbGUuc\n"
+  "HJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5nPgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNr\n"
+  "ZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJheT4KCQkJCTxkaWN0PgoJCQkJCTxrZXk+Y29tLmF\n"
+  "wcGxlLnByaW50LlBhcGVySW5mby5QTVVuYWRqdXN0ZWRQYWdlUmVjdDwva2V5PgoJCQkJCTxhcn\n"
+  "JheT4KCQkJCQkJPHJlYWw+MC4wPC9yZWFsPgoJCQkJCQk8cmVhbD4wLjA8L3JlYWw+CgkJCQkJC\n"
+  "TxyZWFsPjczNDwvcmVhbD4KCQkJCQkJPHJlYWw+NTc2PC9yZWFsPgoJCQkJCTwvYXJyYXk+CgkJ\n"
+  "CQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDwva2V5PgoJCQkJCTxzdHJpbmc\n"
+  "+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCQkJCTxrZXk+Y29tLmFwcGxlLn\n"
+  "ByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPGRhdGU+MjAwNy0wMS0zMFQyMjowODo0M\n"
+  "Vo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LnN0YXRlRmxhZzwva2V5\n"
+  "PgoJCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI+CgkJCQk8L2RpY3Q+CgkJCTwvYXJyYXk+CgkJPC9\n"
+  "kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVVuYWRqdXN0ZWRQYXBlcl\n"
+  "JlY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b\n"
+  "3I8L2tleT4KCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5n\n"
+  "PgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJ\n"
+  "heT4KCQkJCTxkaWN0PgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVVuYW\n"
+  "RqdXN0ZWRQYXBlclJlY3Q8L2tleT4KCQkJCQk8YXJyYXk+CgkJCQkJCTxyZWFsPi0xODwvcmVhb\n"
+  "D4KCQkJCQkJPHJlYWw+LTE4PC9yZWFsPgoJCQkJCQk8cmVhbD43NzQ8L3JlYWw+CgkJCQkJCTxy\n"
+  "ZWFsPjU5NDwvcmVhbD4KCQkJCQk8L2FycmF5PgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnR\n"
+  "pY2tldC5jbGllbnQ8L2tleT4KCQkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZX\n"
+  "I8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5P\n"
+  "goJCQkJCTxkYXRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJCTxrZXk+Y29tLmFw\n"
+  "cGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWdlcj4wPC9pbnRlZ2V\n"
+  "yPgoJCQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5PmNvbS5hcHBsZS5wcm\n"
+  "ludC5QYXBlckluZm8ucHBkLlBNUGFwZXJOYW1lPC9rZXk+CgkJPGRpY3Q+CgkJCTxrZXk+Y29tL\n"
+  "mFwcGxlLnByaW50LnRpY2tldC5jcmVhdG9yPC9rZXk+CgkJCTxzdHJpbmc+Y29tLmFwcGxlLnBy\n"
+  "aW50LnBtLlBvc3RTY3JpcHQ8L3N0cmluZz4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V\n"
+  "0Lml0ZW1BcnJheTwva2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGljdD4KCQkJCQk8a2V5PmNvbS5hcH\n"
+  "BsZS5wcmludC5QYXBlckluZm8ucHBkLlBNUGFwZXJOYW1lPC9rZXk+CgkJCQkJPHN0cmluZz5VU\n"
+  "yBMZXR0ZXI8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY2xpZW50\n"
+  "PC9rZXk+CgkJCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5\n"
+  "nPgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPG\n"
+  "RhdGU+MjAwMy0wNy0wMVQxNzo0OTozNlo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpb\n"
+  "nQudGlja2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJCTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCQk8\n"
+  "L2RpY3Q+CgkJCTwvYXJyYXk+CgkJPC9kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2t\n"
+  "ldC5BUElWZXJzaW9uPC9rZXk+CgkJPHN0cmluZz4wMC4yMDwvc3RyaW5nPgoJCTxrZXk+Y29tLm\n"
+  "FwcGxlLnByaW50LnRpY2tldC5wcml2YXRlTG9jazwva2V5PgoJCTxmYWxzZS8+CgkJPGtleT5jb\n"
+  "20uYXBwbGUucHJpbnQudGlja2V0LnR5cGU8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmlu\n"
+  "dC5QYXBlckluZm9UaWNrZXQ8L3N0cmluZz4KCTwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW5\n"
+  "0LnRpY2tldC5BUElWZXJzaW9uPC9rZXk+Cgk8c3RyaW5nPjAwLjIwPC9zdHJpbmc+Cgk8a2V5Pm\n"
+  "NvbS5hcHBsZS5wcmludC50aWNrZXQucHJpdmF0ZUxvY2s8L2tleT4KCTxmYWxzZS8+Cgk8a2V5P\n"
+  "mNvbS5hcHBsZS5wcmludC50aWNrZXQudHlwZTwva2V5PgoJPHN0cmluZz5jb20uYXBwbGUucHJp\n"
+  "bnQuUGFnZUZvcm1hdFRpY2tldDwvc3RyaW5nPgo8L2RpY3Q+CjwvcGxpc3Q+CjhCSU0D6QAAAAA\n"
+  "AeAADAAAASABIAAAAAALeAkD/7v/uAwYCUgNnBSgD/AACAAAASABIAAAAAALYAigAAQAAAGQAAA\n"
+  "ABAAMDAwAAAAF//wABAAEAAAAAAAAAAAAAAABoCAAZAZAAAAAAACAAAAAAAAAAAAAAAAAAAAAAA\n"
+  "AAAAAAAAAAAADhCSU0D7QAAAAAAEABIAAAAAQABAEgAAAABAAE4QklNBCYAAAAAAA4AAAAAAAAA\n"
+  "AAAAP4AAADhCSU0EDQAAAAAABAAAAB44QklNBBkAAAAAAAQAAAAeOEJJTQPzAAAAAAAJAAAAAAA\n"
+  "AAAABADhCSU0ECgAAAAAAAQAAOEJJTScQAAAAAAAKAAEAAAAAAAAAAThCSU0D9QAAAAAASAAvZm\n"
+  "YAAQBsZmYABgAAAAAAAQAvZmYAAQChmZoABgAAAAAAAQAyAAAAAQBaAAAABgAAAAAAAQA1AAAAA\n"
+  "QAtAAAABgAAAAAAAThCSU0D+AAAAAAAcAAA/////////////////////////////wPoAAAAAP//\n"
+  "//////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA///\n"
+  "//////////////////////////wPoAAA4QklNBAgAAAAAABAAAAABAAACQAAAAkAAAAAAOEJJTQ\n"
+  "QeAAAAAAAEAAAAADhCSU0EGgAAAAADRQAAAAYAAAAAAAAAAAAAAGQAAABkAAAACABEAFMAQwAwA\n"
+  "DIAMwAyADUAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAGQAAABkAAAAAAAAAAAA\n"
+  "AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAABudWxsAAAAAgAAAAZib3VuZHN\n"
+  "PYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAA\n"
+  "AAQnRvbWxvbmcAAABkAAAAAFJnaHRsb25nAAAAZAAAAAZzbGljZXNWbExzAAAAAU9iamMAAAABA\n"
+  "AAAAAAFc2xpY2UAAAASAAAAB3NsaWNlSURsb25nAAAAAAAAAAdncm91cElEbG9uZwAAAAAAAAAG\n"
+  "b3JpZ2luZW51bQAAAAxFU2xpY2VPcmlnaW4AAAANYXV0b0dlbmVyYXRlZAAAAABUeXBlZW51bQA\n"
+  "AAApFU2xpY2VUeXBlAAAAAEltZyAAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAA\n"
+  "BUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAAAZAAAAABSZ2h0bG9uZ\n"
+  "wAAAGQAAAADdXJsVEVYVAAAAAEAAAAAAABudWxsVEVYVAAAAAEAAAAAAABNc2dlVEVYVAAAAAEA\n"
+  "AAAAAAZhbHRUYWdURVhUAAAAAQAAAAAADmNlbGxUZXh0SXNIVE1MYm9vbAEAAAAIY2VsbFRleHR\n"
+  "URVhUAAAAAQAAAAAACWhvcnpBbGlnbmVudW0AAAAPRVNsaWNlSG9yekFsaWduAAAAB2RlZmF1bH\n"
+  "QAAAAJdmVydEFsaWduZW51bQAAAA9FU2xpY2VWZXJ0QWxpZ24AAAAHZGVmYXVsdAAAAAtiZ0Nvb\n"
+  "G9yVHlwZWVudW0AAAARRVNsaWNlQkdDb2xvclR5cGUAAAAATm9uZQAAAAl0b3BPdXRzZXRsb25n\n"
+  "AAAAAAAAAApsZWZ0T3V0c2V0bG9uZwAAAAAAAAAMYm90dG9tT3V0c2V0bG9uZwAAAAAAAAALcml\n"
+  "naHRPdXRzZXRsb25nAAAAAAA4QklNBBEAAAAAAAEBADhCSU0EFAAAAAAABAAAAAE4QklNBAwAAA\n"
+  "AACfkAAAABAAAAZAAAAGQAAAEsAAB1MAAACd0AGAAB/9j/4AAQSkZJRgABAgEASABIAAD/7QAMQ\n"
+  "WRvYmVfQ00AAv/uAA5BZG9iZQBkgAAAAAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwPFRgTExUT\n"
+  "ExgRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQODg4UFA4\n"
+  "ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAGQAZA\n"
+  "MBIgACEQEDEQH/3QAEAAf/xAE/AAABBQEBAQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEFAQEBA\n"
+  "QEBAAAAAAAAAAEAAgMEBQYHCAkKCxAAAQQBAwIEAgUHBggFAwwzAQACEQMEIRIxBUFRYRMicYEy\n"
+  "BhSRobFCIyQVUsFiMzRygtFDByWSU/Dh8WNzNRaisoMmRJNUZEXCo3Q2F9JV4mXys4TD03Xj80Y\n"
+  "nlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3EQACAgECBAQDBAUGBwcGBT\n"
+  "UBAAIRAyExEgRBUWFxIhMFMoGRFKGxQiPBUtHwMyRi4XKCkkNTFWNzNPElBhaisoMHJjXC0kSTV\n"
+  "KMXZEVVNnRl4vKzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2JzdHV2d3h5ent8f/\n"
+  "2gAMAwEAAhEDEQA/APLtso1NRc0vP0Rok8NYyPEfijOG2ljBoAJPxKFppZtbS4Rz38kV+OPRDge\n"
+  "T89EPHBfvLjtb3P8A30K/j47cgsrYNxGpPYJpK8RtyXUlvPfsobV0GV0uippLiX3EaMb2/rKgMB\n"
+  "1ghoiNST4BESCjjLmxqmKtvxiXQ0cd0q8E2bjIDWjk9z5I8QW8JaoHcdkUePZJtZD9p8YU/Rsc/\n"
+  "wBNjS5zjDWjUk+SSKYaJLYq+qWeYGQ5lBPLJ3OA8wz2/wDSWni/U3H2AXW2l2oloa0f9LcjSLeU\n"
+  "hJdb/wAyqd387Zt+DZ5SSpVh/9DzO6dw7gGPuVn6ft/kyPkqwlxjw1Rnh24QNWjUeR5TSuDc6bg\n"
+  "fatpsJZQ3sNC4rWfkVYpbi4LAb3aANEkFLp7GHGYxuhAj4K/hYVNDjYGzZ++eSSoSbLZjGgwxul\n"
+  "XNrPqO35FukdmzyXOQeqtqwqRg4o/SOAN9ng3/AMzW02txZ9I+ZHKr241UOcWDaz3uLtSSPEpWu\n"
+  "rR5XPeylmyNr4BIPPCyH2Oc6T8kXNvddkPe/VzjJPxQAJMKeIoNScrPk2MbfddXUNXvcGtPx0Xb\n"
+  "dJ6NXjOD2Dfdw6w9v5LFW+q/1WLA3Ly9LSJaz91p/wDRjl2lOLWwAMbEJErWjRgESYieVdZhsMF\n"
+  "wMt08ldrx/vVivHaOdSgCoud9krmElpba93ASTlr/AP/R83ohr97voiJV/Fq9QvsI+mdPgs1thc\n"
+  "BWO5C38CoOY1g78qOejLiGvknxLAyGtExp5K9uzGt9RrNw7DhRfQKKx6bZIGgPj4rPycLqWVtIs\n"
+  "JGu5skDyTBRZtQNrb1fU8xrtpBaO4MLQxcx1sNuEjt5rMGJR9noY5hF7Wxa8aAnxVvDb6bgHH2z\n"
+  "omk0e64ajUUXnev9Idi5rrWAux7SXNd4E/muS+rHSjm9VbPtZjj1CSJBI+g3+0uh69b+iDG/QcD\n"
+  "u0nQCeFP6l0MZhWX/AJ1xM+QafY1TQlY1a+WABsdXp8Sp27aBH+vZaVbC0ADlVcASwtdOolp8Ct\n"
+  "BjmtGv0uI8EmJmxkIjWkmPEKLSPiidxIgJKRbDPCSN5pJyH//S87uw/suZ6c72iC13kVs9PdDmk\n"
+  "KllVziV3cuafc7yP0QjYFh26cqM6hsxAjIj6u6xzbDHh3R663AaceH+5BwdruVp2PqZUA0a9yo6\n"
+  "DPQajscnXb8YQdzC8H909joiZttoxoBIa4gGOQ3uqh+z1RuD2Ds4j2n+39FNKaFMevS/p5LPpSA\n"
+  "I8/b/ABW70THZXj11VjaIAIHgFl5VdD8AneDMaec6Lb6QAKmu7x+VSw2a3MdF/rF9YKeh4B9OHZ\n"
+  "lpAprPH8p7v5DFwrPrV9YDa645toLjMaFo8mtcEvrhkWZHXbg7T0Q2to8o3f8AfkarEitlVTKnY\n"
+  "ra992QQ2wOfG57H2bg7H2fzbFKA130P6n9dHWemCx5H2mk7LgPH818f8IuhAka6ea8y/wAWrcod\n"
+  "VyrceRhBsPae5J/Qj+sxq9KDpMuMuKBCEntnny/1CSaWxM6pIKf/0/MvtF3pCrefTBnbOi1elP3\n"
+  "Et8Vi+Sv9LuNd4HwTSNGSEqkLerwtwn+SYKtOeS4A8Krh2j1D/K1Vu4B5gaDmVDJtAr7zYYGoRB\n"
+  "QDWQ4c9h/csuyjI3fobnDyJR8fF6ltcTaXRwCkAuAsfMGr1TGFNdTmEwLWS2dIK6npLgK2T4Lle\n"
+  "pUZxoc9+6K4eR5NO5bPT73NoqIfoILT/JcFJDZr8zGiNXnvrfiur6/Y8tht7WvaexgbXf8AUrFt\n"
+  "8IExyvRusYDOsYTAIbfWdzHRJ8HN/tLj7OgdRZawmreHP2gt9wEfvtH0f7SkDXe7+o+AOn9DquL\n"
+  "f0mV+leQPH6H+axafUvrB07ptJtyshtTZDTEudJ7bWS5V6MmyltVLn7ht2hwECQP+isb60/Vqvr\n"
+  "tbLsa1lObVIJd9Gxv5rXx9F7fzHpIbf/jgfVnd/TLYj6XoOhJcP/zE+sOzd6dW7dt2eo3dH7/9R\n"
+  "JJWj//U8uiGFx76BFZLQ2xvLeVGAWQrFDJbtKBSHd6blNura4H3BbDXB7InVcZXZdh2bmTt7hbO\n"
+  "J1dj2gzCjlFnhPod3WLHB+n3o9ZsAkFVMfMrs7orLmgkHUdkyqZQQWWQbLGlrjMjUeSrfV3Ltsw\n"
+  "30EBzcd5YCedvLETJya66nWOIAaCVnfU/KuZn21CDVa02PngQdHf9LapMfVhzkaAPUUW3M91YaR\n"
+  "3YDJ+WiBmZGazPo9Kttdt2j63E6s/fft/d/NWjXkMra7KtO2qkE6cErHpvsyMmzPu0dY4Bg/dYP\n"
+  "otTpyoaMUI2XUya8tzG/pi0NMtICo/bsut21gdcWclkj5OncxaDrw6kM+9QxQzaWRAGii4pDqzC\n"
+  "MT02aX7WzPU9b7PrG3bvO6P6yStfZm+pHnPySS4590+3jf/V8yb+CsUbp8uyz0kDskbu2dmz9J8\n"
+  "lSt9Ld+gn1O8cKikmxXydbH+3bhsmfwWj/lONYlcwkhL6L4bfpOxn/tD0/wBN/N944Wh9VJm/b/\n"
+  "O+347df+/rl0k+O38GLJ83X/CfTOt7v2dV6P8AMbx6njHb/pKuN3pN2+IXnaSjybr8e31fUqd+0\n"
+  "Sj487DHMryZJMXjq+sfpPX84SXk6SSX/9kAOEJJTQQhAAAAAABVAAAAAQEAAAAPAEEAZABvAGIA\n"
+  "ZQAgAFAAaABvAHQAbwBzAGgAbwBwAAAAEwBBAGQAbwBiAGUAIABQAGgAbwB0AG8AcwBoAG8AcAA\n"
+  "gADcALgAwAAAAAQA4QklNBAYAAAAAAAcABQAAAAEBAP/hFWdodHRwOi8vbnMuYWRvYmUuY29tL3\n"
+  "hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0n77u/JyBpZD0nVzVNME1wQ2VoaUh6cmVTek5UY3prY\n"
+  "zlkJz8+Cjw/YWRvYmUteGFwLWZpbHRlcnMgZXNjPSJDUiI/Pgo8eDp4YXBtZXRhIHhtbG5zOng9\n"
+  "J2Fkb2JlOm5zOm1ldGEvJyB4OnhhcHRrPSdYTVAgdG9vbGtpdCAyLjguMi0zMywgZnJhbWV3b3J\n"
+  "rIDEuNSc+CjxyZGY6UkRGIHhtbG5zOnJkZj0naHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi\n"
+  "1yZGYtc3ludGF4LW5zIycgeG1sbnM6aVg9J2h0dHA6Ly9ucy5hZG9iZS5jb20vaVgvMS4wLyc+C\n"
+  "gogPHJkZjpEZXNjcmlwdGlvbiBhYm91dD0ndXVpZDoyMmQwMmIwYS1iMjQ5LTExZGItOGFmOC05\n"
+  "MWQ1NDAzZjkyZjknCiAgeG1sbnM6cGRmPSdodHRwOi8vbnMuYWRvYmUuY29tL3BkZi8xLjMvJz4\n"
+  "KICA8IS0tIHBkZjpTdWJqZWN0IGlzIGFsaWFzZWQgLS0+CiA8L3JkZjpEZXNjcmlwdGlvbj4KCi\n"
+  "A8cmRmOkRlc2NyaXB0aW9uIGFib3V0PSd1dWlkOjIyZDAyYjBhLWIyNDktMTFkYi04YWY4LTkxZ\n"
+  "DU0MDNmOTJmOScKICB4bWxuczpwaG90b3Nob3A9J2h0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9z\n"
+  "aG9wLzEuMC8nPgogIDwhLS0gcGhvdG9zaG9wOkNhcHRpb24gaXMgYWxpYXNlZCAtLT4KIDwvcmR\n"
+  "mOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gYWJvdXQ9J3V1aWQ6MjJkMDJiMGEtYj\n"
+  "I0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5JwogIHhtbG5zOnhhcD0naHR0cDovL25zLmFkb2JlL\n"
+  "mNvbS94YXAvMS4wLyc+CiAgPCEtLSB4YXA6RGVzY3JpcHRpb24gaXMgYWxpYXNlZCAtLT4KIDwv\n"
+  "cmRmOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gYWJvdXQ9J3V1aWQ6MjJkMDJiMGE\n"
+  "tYjI0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5JwogIHhtbG5zOnhhcE1NPSdodHRwOi8vbnMuYW\n"
+  "RvYmUuY29tL3hhcC8xLjAvbW0vJz4KICA8eGFwTU06RG9jdW1lbnRJRD5hZG9iZTpkb2NpZDpwa\n"
+  "G90b3Nob3A6MjJkMDJiMDYtYjI0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5PC94YXBNTTpEb2N1\n"
+  "bWVudElEPgogPC9yZGY6RGVzY3JpcHRpb24+CgogPHJkZjpEZXNjcmlwdGlvbiBhYm91dD0ndXV\n"
+  "pZDoyMmQwMmIwYS1iMjQ5LTExZGItOGFmOC05MWQ1NDAzZjkyZjknCiAgeG1sbnM6ZGM9J2h0dH\n"
+  "A6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvJz4KICA8ZGM6ZGVzY3JpcHRpb24+CiAgIDxyZ\n"
+  "GY6QWx0PgogICAgPHJkZjpsaSB4bWw6bGFuZz0neC1kZWZhdWx0Jz4gICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgIDwvcmRmOkFsdD4KICA8L2RjOmRlc2NyaXB0aW9\n"
+  "uPgogPC9yZGY6RGVzY3JpcHRpb24+Cgo8L3JkZjpSREY+CjwveDp4YXBtZXRhPgogICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA\n"
+  "ogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+  "ICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+  "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAg\n"
+  "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+  "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIC\n"
+  "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0ndyc/P\n"
+  "v/uAA5BZG9iZQBkQAAAAAH/2wCEAAQDAwMDAwQDAwQGBAMEBgcFBAQFBwgGBgcGBggKCAkJCQkI\n"
+  "CgoMDAwMDAoMDAwMDAwMDAwMDAwMDAwMDAwMDAwBBAUFCAcIDwoKDxQODg4UFA4ODg4UEQwMDAw\n"
+  "MEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAGQAZAMBEQACEQEDEQ\n"
+  "H/3QAEAA3/xAGiAAAABwEBAQEBAAAAAAAAAAAEBQMCBgEABwgJCgsBAAICAwEBAQEBAAAAAAAAA\n"
+  "AEAAgMEBQYHCAkKCxAAAgEDAwIEAgYHAwQCBgJzAQIDEQQABSESMUFRBhNhInGBFDKRoQcVsUIj\n"
+  "wVLR4TMWYvAkcoLxJUM0U5KismNzwjVEJ5OjszYXVGR0w9LiCCaDCQoYGYSURUaktFbTVSga8uP\n"
+  "zxNTk9GV1hZWltcXV5fVmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9zhIWGh4iJiouMjY6PgpOUlZaX\n"
+  "mJmam5ydnp+So6SlpqeoqaqrrK2ur6EQACAgECAwUFBAUGBAgDA20BAAIRAwQhEjFBBVETYSIGc\n"
+  "YGRMqGx8BTB0eEjQhVSYnLxMyQ0Q4IWklMlomOywgdz0jXiRIMXVJMICQoYGSY2RRonZHRVN/Kj\n"
+  "s8MoKdPj84SUpLTE1OT0ZXWFlaW1xdXl9UZWZnaGlqa2xtbm9kdXZ3eHl6e3x9fn9zhIWGh4iJi\n"
+  "ouMjY6Pg5SVlpeYmZqbnJ2en5KjpKWmp6ipqqusra6vr/2gAMAwEAAhEDEQA/APBnplwPAdR+GB\n"
+  "KY6dYtNG1w39yh4+xb+zIksgEfFaRSSoIx8f7RPRRkSWQimM+lRmwWVXFWYigHxUUVoMiJM+Fj0\n"
+  "tg0RBegLE0Wu+3c+GTBazFCGI7HtSp9slbFYYzyoBsegw2hY1Afl3wqqRqahk+0tDgKpgu4DAUU\n"
+  "+HY+GRS2ePiMKtUB3G+KGuONq//Q8OzpFbW5WnxMop4k9crG5ZnZNJkEOn21utVRYw7HxZtz+OR\n"
+  "vdsrZ2lRtci4aVxFEQA0neg/ZXxJpTITNNuOFss0vSotYNvZ2qGRkPKSTqiU8Sdqk5SZU5Ix8XJ\n"
+  "NNZ8k6bp8TtM73OputUtYq0Unux/hkRkJOzZLCAN2KR+VpbtSkCBaDnIzdlWu59u+XeJTjeASk8\n"
+  "+juZOESEAVqx8BvU/PJibScTrTy09560hkWOGFd2YgFnPQKD19zhOSkxw2l8Vm6XAiYb8gg+k5O\n"
+  "9mnhoon9H3cs5s7WF5pp29OGGMFndyaAKBuTiEEPQLD8h/NDmNdYlttNkYjlbFjcXCr3LLH8II8\n"
+  "C2WUGviZvon/OPWkm3RNSv72SYllMkKxQRV67CQMSKYQAxMkR/wBC56d61P0heel4cYuVOXWvTp\n"
+  "h4Qjjf/9Hw5qBYyISaqjBV+QpvkAzKcki4HomnIxck/wBhtlR2bhunvlDywddMUl4zW+kQ9FQ8X\n"
+  "nfuSewrtmPkycPvc/DhMhvyegXOrWWhmLQPKlsj6xIAiLCoZkY96nv7npmJvI2XOjQFMl0fyRqM\n"
+  "NoxvZvrGt33wlATwiMnVnY1LEdSfuyXF3KIDmUu88w2XlnTl8raAlb2ZFfVL0jdYRtQnxc7BfDC\n"
+  "OaJR7nm3me5tdOtjbMvp3ZRXkV6chVQRX79hmVjgZG+jgZ5jHGhzecXF5LPL6jEjstSSaDM51Ka\n"
+  "6MZ9S1C0sEBe8uZo4YCBXdjxGw60wEWyEqfUHkT8vLXRJFuLdTcaqfhlvWUErtukZ3ABPUjIXTE\n"
+  "m3rGmeV2Tk5UKz/AG/E/wAcgZKya20C3b02kjYtH8AqCygbkUH0nLYlgUb+gbWtPbpXt/n2ybB/\n"
+  "/9Lw4oaVxGd+PxH3qBkGaY3KyiSP01IkiUclH8sg+LKydm6INvZvKsFu+kWtvD8LRoFNRup6moO\n"
+  "aqd277HsGW+XPLmn6XM17FF6l7vW4fd2Zuu+RFls2tmUNrLJb7TSBertGQGqetDkxE0na0pvtHs\n"
+  "QkszWyiGAG5laYlnkeMVHJj8sA5rPk+SvMepTalqlxd3B5zTOXdj/MxqafLpm5xioh5nPK5kpRG\n"
+  "pkcKAST0A6k5NpfUP5K/ki1ssHmHzF+71KRQ8Nud/Qibb/kYw6/yjbrXISlSH07YaHbWyxx2kXE\n"
+  "KACB2zHJtLI7XSelBRvH2xCpvaaTDHXkOTVBPcUG2479RlsdmJVPRtvV+ylenQ0y62FP/9PxRpo\n"
+  "WG5FxKKxKFDA+GVS5NsebLdFsRePc3siVW4f4QR0QVAGYeSXR2unhtZ6s60K6jt+MMSFwtF2+xX\n"
+  "wr7eGUGLlRPQMsE2vxQm7itxKg3VCfT2+nb8cDYaCDtfOXmCCcROrQrUhkkCHYn6emRMqZxjbLd\n"
+  "F1+W/4xajHzjNCtQKMffETWUdngX5p+QZ9A8xS6hbo0ui37NNDPT7DOalHpsCD08Rmyw5ARTpdV\n"
+  "gIPEF35MeRn80ed4S5EdrpKm9kZ15K0iH92hB7Me/tmS60vt/QrCYyekiBdgSTXcjqV9q9MokFD\n"
+  "N7S3aFVVR8RoK9zldqndvAY6nffr/AGYQqLhjdpCoIAZW22HavU/LJBUP9WblX0xTw7fOmWsX/9\n"
+  "Tw7FdvMqWkQ3Z1qfED+mQIbI77PX/LFis9vBajZm2Y+x65rMh3t30Bsze400aVaIbSLk6r8CMRT\n"
+  "l/NmOcllnGDD9Y8uecNfEEiXrMgDGWAyGOOu5WlB+vMrHODTlxZCdjsyFdB006VpVtLasurQxBL\n"
+  "64WiLI4/aFT1ANOXemV5piR2b9NiljB4yyHy9CLOVI5GJhB+CvXY9R8xmINzs5HNZ+Z96BZpbxA\n"
+  "fVJo39UFefwopYgL4nMiMd2qZoIn/AJx00u3t/Lt7qpp9Yv5GLf5MUTERqfbvmzBeezjd9H+VlL\n"
+  "wSQzBqsvOGQD7L12rXsemPNxmXQSxxIPU2nFV4HYqR1xEUWj4ZAxBryr2G+J2VGDZlLrxUH6KZA\n"
+  "Fkqb15VFelfwy+2FP8A/9Xxlf6AdA182Yk9eFeLxSjoVfcfSMo4uIOfkweFOnpvlWYrLEwNFAA+\n"
+  "nMOYdrhFvQLeSO7coBXiK8iKiv07Zj8Ac4QtNrW1njUcKcT+yAR/xGmR4WcsStLpTuPU9IFaEsV\n"
+  "BP3k4m2AgBzSwyQNcIwNTE1aI3wnam9O2Ug7s5Ckk/NDndeVXa2H78MqqV6jmeBp9+ZWKXqDjZ4\n"
+  "+gvVvy30qCy0qzsLRBCnBI2VdgUTqPvOZ7y+Q7pz+bn5q6d+VflZxZlJ/NN4ypptk5qtB9qRwDX\n"
+  "gn/AAx2y2ItpfKFv+eH5qNeTajJ5ovVaVywSqvEtTUKqupAA6D2y0BNPtv/AJx//M5PzL8mJeXT\n"
+  "L+ndPf6rqarSpkAqsnEAAeoN6DpkJRYci9lROSgSUUH9o9K5Tw0ztfSHnXkOtK9q+PHwydq//9b\n"
+  "yxrVoZNBtNSA5zRMPXmH8j0CLXuBmHE+qneamHpEuqYeV7pzFVTRgQK5XMNmnlb1vyyY5QA1OwJ\n"
+  "+eUF2seTOLu5s7azVIVAkpVn/hhnIALG73Yz5jvb1dICqzpDNIqyFD8SxH7R28cxibZCiWOsdJs\n"
+  "PTM6XNstPhnkjIhcHuJBVfvOCiUSn0TfWrTTLjyw8guA/PifTO3xcxxA8a5ZAbimvJP0m3p/kFF\n"
+  "WxhmpWQJ9NW3zZPHz5vlb/nIDVbrWfzO1RJhxGnpDaRL/khA1T7ktmSOTAJhZaAUtLawsbayl8v\n"
+  "xWi3Gpay0cF3HPcFRJJHJMXVrcJ8UaAFG5LWjF8tAYW9H/wCcOo9bTzxrt/owkTyksZW5gkIKvI\n"
+  "7k26nvyReRJHyyBWT7dWQyOWlbnK2526e1O1MqIUFE84uPLkOdK9RXI0E2/wD/1/DA1bURZLY/W\n"
+  "ZDZqwb0eXw7dMgIi7bjllVXsz7yNcfWC0Vd3Ip92Y2UOz0cnsPlwyx8xQ/u24sMxCadoJp9LOXk\n"
+  "VX/uwRUE0BI8cokbLMyoKouHu2MaKGXw7fLDwgoGSkbHpaNZyLLHRSKcFFQQRvUdMlwUFOQyLzr\n"
+  "ztpCaba6fPau4ijv4OURY8AjVFKV7ZZiO+7Vnh6XvXkSWNbW2WTb92KDxIFMzwHlZc3zX+fuizW\n"
+  "f5p3ty8XGDU4YLmCQiisyII3+4rvl8UB5ffEghRGvOm7AbnvWvjk1fen/ONPldPKP5aWOpPCfr2\n"
+  "uE31y6q2wbaMEn+VAMDSdyzrzj+avlHyTp0l/r2rxWFuHWJuIeacu4qFCRgsajfBwsty89/6Gr/\n"
+  "ACa9an+JL/hSnrfoubhXwpXpjwhaL//Q8E1AqtcAZMs8l6i1nqMa1oSVP0VynKLDmaWdSfQXl69\n"
+  "jF1Jv8MhDb5rpB3AO7INRRLhhGp4R05FgaGvTMU8200xS70zVDMRp2pTIOvBmB3PgQP15kxIcnD\n"
+  "LH/EEz0rRvOJhldr9pQtCqyd6VrShGTqw5d4ARv9jHfOGl+ZJNMluLkyenaFbiRdqFYW5nrWuwO\n"
+  "MKB5MdSMRxnhlu9N8p6lLFpti63FUjCtFJTrDKvse2bEDZ4XJ9RZB+YPli2/Mjy5bxoUi1a0YS2\n"
+  "85UOwIXiy9jRu+TBppfOF1+V3m22vrdpNPM8cs/oo0VJlUqQPjValR3+IZNNvtLS9Yu9Mi0/TJr\n"
+  "kyp6QhWVVCIWRATsKBemwwFrDzT87fybs/wA1bW21PRb+DTvNlgGSRp6iC8i3KJJx+y6n7D0Pwm\n"
+  "hxBZXT55/6Fi/Nf0PW+qWXq+t6X1X67F6vD/ftK04V/wBl344U8b//0fBapxheVh9ocV+nviqY2\n"
+  "/qQJDew/bioWHiuQ8m0bbvaPKGtQ6jaxSo9JloCK75gZI0Xb4sgkHo8MouoAvP94BsRmGY7uWJU\n"
+  "gzbypOQpNOvIdK4Nw2WCE2tXulTkjEEbdafgclxMhFBas93dwyQzsWDghlJFONKHJCZtjOFBJfy\n"
+  "j1y9vPL9zpbIs0WkXL2sUjA8hDXlGCRXtt07ZuYvL5KJeo6bfajbkzWkcToR8dqshZ6in2fhNK/\n"
+  "PDTUlXmHVvMdr5o0v9H2kdrqGpfu7m0nkY87Uf7tkKAU4/s03ynLkEBbfihx7dGT6va67LbRMNR\n"
+  "aKOBuUTKgIBXoK1BOYR1M3aQ0mOt9yxUeZNdtJhFapLqMluSXkg5oxJrUMW5KevQ9MmNXXNqOiH\n"
+  "Rr/Hmv8A1r9I/oj95w+r+j9Yf1+NP5+nXtTD+dF8tkfkOlv/0vC3ph7f0/alcVTbS4A8QibuKb5\n"
+  "RI05EBYRFpdX3ly79a2qYCavH/EY7TCYyMD5PSdD8+wXUSn1ArDqOhBzFlipz4ZwWbaV5htbsgF\n"
+  "qg9crMXKErGyYwajFGzxyHlGSePbbwyqg5UZlCaxrFpaWU95LIqrEjMAT4Dp9OShGy1ZslBhv/A\n"
+  "Dj9rd/a+aL+xUK+m38L3d0HrxRo2HFtu5D8c27y8t30raarbWkU+u6g4gsNORn+EcUaSh2Pc0/4\n"
+  "lgtAjezzbT9SutY1i782al8Nxdyotqh6xWybIg+jc5q8s+I27bFDgFPQp9RE+nrag70+L6crrZu\n"
+  "4jajokdv6LW/Dii1Wo61PXKQN3KPK0L+h4/rnD/K5V78a5LhXxd3/0/DMXXtwxVNtL9Xkaf3f7N\n"
+  "etfbKMjdjtkZ9D6ufrlK0+HpX8coF9HJ26sXvfqXrf7i/U+uften/d/wCyrmQL6uOav0pvpP8Ai\n"
+  "b1F+rV59+vH6a5XLhcjH4nRmY/xpxHP0/UptWvT6Mx/RbmjxWK+aP8AFf1M/pCv1Kvxen9inavf\n"
+  "MrFwXtzcLUeLXq5Mv/I3nz1b0v8AjofuKVry9KrUpTanOlf9jmQ68va/zH9b/COn/o7/AI431mP\n"
+  "65SvLh+zWvbl9rMfNfC34K4kmj9T6lD6FKclp/DNYXZx5srsPrHor6nXvkgxTPS/U+rv6dPU5mt\n"
+  "fngFN5ulv+l/pL/Lp/scerHo//2Q==\n";
+
+static std::string gCommandLine;
+
+TEST(Base64, LargeSample) {
+  LOG(LS_VERBOSE) << "Testing specific base64 file";
+
+  char unescaped[64 * 1024];
+
+  // unescape that massive blob above
+  size_t size = Base64Unescape(SpecificTest,
+                            sizeof(SpecificTest),
+                            unescaped,
+                            sizeof(unescaped));
+
+  EXPECT_EQ(size, sizeof(testbase64));
+  EXPECT_EQ(0, memcmp(testbase64, unescaped, sizeof(testbase64)));
+}
+
+bool DecodeTest(const char* encoded, size_t expect_unparsed,
+                const char* decoded, Base64::DecodeFlags flags)
+{
+  std::string result;
+  size_t consumed = 0, encoded_len = strlen(encoded);
+  bool success = Base64::DecodeFromArray(encoded, encoded_len, flags,
+                                         &result, &consumed);
+  size_t unparsed = encoded_len - consumed;
+  EXPECT_EQ(expect_unparsed, unparsed) << "\"" << encoded
+                                       << "\" -> \"" << decoded
+                                       << "\"";
+  EXPECT_STREQ(decoded, result.c_str());
+  return success;
+}
+
+#define Flags(x,y,z) \
+  Base64::DO_PARSE_##x | Base64::DO_PAD_##y | Base64::DO_TERM_##z
+
+TEST(Base64, DecodeParseOptions) {
+  // Trailing whitespace
+  EXPECT_TRUE (DecodeTest("YWJjZA== ", 1, "abcd", Flags(STRICT, YES, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA== ", 0, "abcd", Flags(WHITE,  YES, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA== ", 0, "abcd", Flags(ANY,    YES, CHAR)));
+
+  // Embedded whitespace
+  EXPECT_FALSE(DecodeTest("YWJjZA= =", 3, "abcd", Flags(STRICT, YES, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA= =", 0, "abcd", Flags(WHITE,  YES, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA= =", 0, "abcd", Flags(ANY,    YES, CHAR)));
+
+  // Embedded non-base64 characters
+  EXPECT_FALSE(DecodeTest("YWJjZA=*=", 3, "abcd", Flags(STRICT, YES, CHAR)));
+  EXPECT_FALSE(DecodeTest("YWJjZA=*=", 3, "abcd", Flags(WHITE,  YES, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA=*=", 0, "abcd", Flags(ANY,    YES, CHAR)));
+
+  // Unexpected padding characters
+  EXPECT_FALSE(DecodeTest("YW=JjZA==", 7, "a",    Flags(STRICT, YES, CHAR)));
+  EXPECT_FALSE(DecodeTest("YW=JjZA==", 7, "a",    Flags(WHITE,  YES, CHAR)));
+  EXPECT_TRUE (DecodeTest("YW=JjZA==", 0, "abcd", Flags(ANY,    YES, CHAR)));
+}
+
+TEST(Base64, DecodePadOptions) {
+  // Padding
+  EXPECT_TRUE (DecodeTest("YWJjZA==",  0, "abcd", Flags(STRICT, YES, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA==",  0, "abcd", Flags(STRICT, ANY, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA==",  2, "abcd", Flags(STRICT, NO,  CHAR)));
+
+  // Incomplete padding
+  EXPECT_FALSE(DecodeTest("YWJjZA=",   1, "abcd", Flags(STRICT, YES, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA=",   1, "abcd", Flags(STRICT, ANY, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA=",   1, "abcd", Flags(STRICT, NO,  CHAR)));
+
+  // No padding
+  EXPECT_FALSE(DecodeTest("YWJjZA",    0, "abcd", Flags(STRICT, YES, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA",    0, "abcd", Flags(STRICT, ANY, CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJjZA",    0, "abcd", Flags(STRICT, NO,  CHAR)));
+}
+
+TEST(Base64, DecodeTerminateOptions) {
+  // Complete quantum
+  EXPECT_TRUE (DecodeTest("YWJj",      0, "abc",  Flags(STRICT, NO,  BUFFER)));
+  EXPECT_TRUE (DecodeTest("YWJj",      0, "abc",  Flags(STRICT, NO,  CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJj",      0, "abc",  Flags(STRICT, NO,  ANY)));
+
+  // Complete quantum with trailing data
+  EXPECT_FALSE(DecodeTest("YWJj*",     1, "abc",  Flags(STRICT, NO,  BUFFER)));
+  EXPECT_TRUE (DecodeTest("YWJj*",     1, "abc",  Flags(STRICT, NO,  CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJj*",     1, "abc",  Flags(STRICT, NO,  ANY)));
+
+  // Incomplete quantum
+  EXPECT_FALSE(DecodeTest("YWJ",       0, "ab",   Flags(STRICT, NO,  BUFFER)));
+  EXPECT_FALSE(DecodeTest("YWJ",       0, "ab",   Flags(STRICT, NO,  CHAR)));
+  EXPECT_TRUE (DecodeTest("YWJ",       0, "ab",   Flags(STRICT, NO,  ANY)));
+}
diff --git a/talk/base/basicpacketsocketfactory.cc b/talk/base/basicpacketsocketfactory.cc
index 42721ba..d1da08e 100644
--- a/talk/base/basicpacketsocketfactory.cc
+++ b/talk/base/basicpacketsocketfactory.cc
@@ -157,7 +157,8 @@
   } else {
     // Otherwise, try to find a port in the provided range.
     for (int port = min_port; ret < 0 && port <= max_port; ++port) {
-      ret = socket->Bind(talk_base::SocketAddress(local_address.ip(), port));
+      ret = socket->Bind(talk_base::SocketAddress(local_address.ipaddr(),
+                                                  port));
     }
   }
   return ret;
diff --git a/talk/base/basictypes.h b/talk/base/basictypes.h
index 4193ede..4840631 100644
--- a/talk/base/basictypes.h
+++ b/talk/base/basictypes.h
@@ -25,12 +25,12 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef TALK_BASE_BASICTYPES_H__
-#define TALK_BASE_BASICTYPES_H__
+#ifndef TALK_BASE_BASICTYPES_H_
+#define TALK_BASE_BASICTYPES_H_
 
 #include <stddef.h>  // for NULL, size_t
 
-#ifndef WIN32
+#if !(defined(_MSC_VER) && (_MSC_VER < 1600))
 #include <stdint.h>  // for uintptr_t
 #endif
 
@@ -40,7 +40,7 @@
 
 #include "talk/base/constructormagic.h"
 
-#ifndef INT_TYPES_DEFINED
+#if !defined(INT_TYPES_DEFINED)
 #define INT_TYPES_DEFINED
 #ifdef COMPILER_MSVC
 typedef unsigned __int64 uint64;
@@ -53,7 +53,18 @@
 #endif
 #define INT64_F "I64"
 #else  // COMPILER_MSVC
-#ifdef __LP64__
+// On Mac OS X, cssmconfig.h defines uint64 as uint64_t
+#if defined(OSX)
+typedef uint64_t uint64;
+typedef int64_t int64;
+#ifndef INT64_C
+#define INT64_C(x) x ## L
+#endif
+#ifndef UINT64_C
+#define UINT64_C(x) x ## UL
+#endif
+#define INT64_F "l"
+#elif defined(__LP64__)
 typedef unsigned long uint64;
 typedef long int64;
 #ifndef INT64_C
@@ -80,7 +91,7 @@
 typedef unsigned short uint16;
 typedef short int16;
 typedef unsigned char uint8;
-typedef char int8;
+typedef signed char int8;
 #endif  // INT_TYPES_DEFINED
 
 #ifdef WIN32
@@ -102,15 +113,10 @@
 #define CPU_X86 1
 #endif
 
-#ifdef WIN32
-#define alignof(t) __alignof(t)
-#else  // !WIN32
-#define alignof(t) __alignof__(t)
-#endif  // !WIN32
-#define IS_ALIGNED(p, a) (0==(reinterpret_cast<uintptr_t>(p) & ((a)-1)))
 #define ALIGNP(p, t) \
   (reinterpret_cast<uint8*>(((reinterpret_cast<uintptr_t>(p) + \
   ((t)-1)) & ~((t)-1))))
+#define IS_ALIGNED(p, a) (!((uintptr_t)(p) & ((a) - 1)))
 
 #ifndef UNUSED
 #define UNUSED(x) Unused(static_cast<const void *>(&x))
@@ -127,4 +133,9 @@
 #define GCC_ATTR(x)
 #endif  // !__GNUC__
 
-#endif // TALK_BASE_BASICTYPES_H__
+// Use these to declare and define a static local variable (static T;) so that
+// it is leaked so that its destructors are not called at exit.
+#define LIBJINGLE_DEFINE_STATIC_LOCAL(type, name, arguments) \
+  static type& name = *new type arguments
+
+#endif // TALK_BASE_BASICTYPES_H_
diff --git a/talk/base/buffer_unittest.cc b/talk/base/buffer_unittest.cc
new file mode 100644
index 0000000..b0aa243
--- /dev/null
+++ b/talk/base/buffer_unittest.cc
@@ -0,0 +1,160 @@
+/*
+ * 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/buffer.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+static const char kTestData[] = {
+  0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
+};
+
+TEST(BufferTest, TestConstructDefault) {
+  Buffer buf;
+  EXPECT_EQ(0U, buf.length());
+  EXPECT_EQ(0U, buf.capacity());
+  EXPECT_EQ(Buffer(), buf);
+}
+
+TEST(BufferTest, TestConstructEmptyWithCapacity) {
+  Buffer buf(NULL, 0, 256U);
+  EXPECT_EQ(0U, buf.length());
+  EXPECT_EQ(256U, buf.capacity());
+  EXPECT_EQ(Buffer(), buf);
+}
+
+TEST(BufferTest, TestConstructData) {
+  Buffer buf(kTestData, sizeof(kTestData));
+  EXPECT_EQ(sizeof(kTestData), buf.length());
+  EXPECT_EQ(sizeof(kTestData), buf.capacity());
+  EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+  EXPECT_EQ(Buffer(kTestData, sizeof(kTestData)), buf);
+}
+
+TEST(BufferTest, TestConstructDataWithCapacity) {
+  Buffer buf(kTestData, sizeof(kTestData), 256U);
+  EXPECT_EQ(sizeof(kTestData), buf.length());
+  EXPECT_EQ(256U, buf.capacity());
+  EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+  EXPECT_EQ(Buffer(kTestData, sizeof(kTestData)), buf);
+}
+
+TEST(BufferTest, TestConstructCopy) {
+  Buffer buf1(kTestData, sizeof(kTestData), 256), buf2(buf1);
+  EXPECT_EQ(sizeof(kTestData), buf2.length());
+  EXPECT_EQ(sizeof(kTestData), buf2.capacity());  // capacity isn't copied
+  EXPECT_EQ(0, memcmp(buf2.data(), kTestData, sizeof(kTestData)));
+  EXPECT_EQ(buf1, buf2);
+}
+
+TEST(BufferTest, TestAssign) {
+  Buffer buf1, buf2(kTestData, sizeof(kTestData), 256);
+  EXPECT_NE(buf1, buf2);
+  buf1 = buf2;
+  EXPECT_EQ(sizeof(kTestData), buf1.length());
+  EXPECT_EQ(sizeof(kTestData), buf1.capacity());  // capacity isn't copied
+  EXPECT_EQ(0, memcmp(buf1.data(), kTestData, sizeof(kTestData)));
+  EXPECT_EQ(buf1, buf2);
+}
+
+TEST(BufferTest, TestSetData) {
+  Buffer buf;
+  buf.SetData(kTestData, sizeof(kTestData));
+  EXPECT_EQ(sizeof(kTestData), buf.length());
+  EXPECT_EQ(sizeof(kTestData), buf.capacity());
+  EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestAppendData) {
+  Buffer buf(kTestData, sizeof(kTestData));
+  buf.AppendData(kTestData, sizeof(kTestData));
+  EXPECT_EQ(2 * sizeof(kTestData), buf.length());
+  EXPECT_EQ(2 * sizeof(kTestData), buf.capacity());
+  EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+  EXPECT_EQ(0, memcmp(buf.data() + sizeof(kTestData),
+                      kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestSetLengthSmaller) {
+  Buffer buf;
+  buf.SetData(kTestData, sizeof(kTestData));
+  buf.SetLength(sizeof(kTestData) / 2);
+  EXPECT_EQ(sizeof(kTestData) / 2, buf.length());
+  EXPECT_EQ(sizeof(kTestData), buf.capacity());
+  EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData) / 2));
+}
+
+TEST(BufferTest, TestSetLengthLarger) {
+  Buffer buf;
+  buf.SetData(kTestData, sizeof(kTestData));
+  buf.SetLength(sizeof(kTestData) * 2);
+  EXPECT_EQ(sizeof(kTestData) * 2, buf.length());
+  EXPECT_EQ(sizeof(kTestData) * 2, buf.capacity());
+  EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestSetCapacitySmaller) {
+  Buffer buf;
+  buf.SetData(kTestData, sizeof(kTestData));
+  buf.SetCapacity(sizeof(kTestData) / 2);  // should be ignored
+  EXPECT_EQ(sizeof(kTestData), buf.length());
+  EXPECT_EQ(sizeof(kTestData), buf.capacity());
+  EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestSetCapacityLarger) {
+  Buffer buf(kTestData, sizeof(kTestData));
+  buf.SetCapacity(sizeof(kTestData) * 2);
+  EXPECT_EQ(sizeof(kTestData), buf.length());
+  EXPECT_EQ(sizeof(kTestData) * 2, buf.capacity());
+  EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestSetCapacityThenSetLength) {
+  Buffer buf(kTestData, sizeof(kTestData));
+  buf.SetCapacity(sizeof(kTestData) * 4);
+  memcpy(buf.data() + sizeof(kTestData), kTestData, sizeof(kTestData));
+  buf.SetLength(sizeof(kTestData) * 2);
+  EXPECT_EQ(sizeof(kTestData) * 2, buf.length());
+  EXPECT_EQ(sizeof(kTestData) * 4, buf.capacity());
+  EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+  EXPECT_EQ(0, memcmp(buf.data() + sizeof(kTestData),
+                      kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestTransfer) {
+  Buffer buf1(kTestData, sizeof(kTestData), 256U), buf2;
+  buf1.TransferTo(&buf2);
+  EXPECT_EQ(0U, buf1.length());
+  EXPECT_EQ(0U, buf1.capacity());
+  EXPECT_EQ(sizeof(kTestData), buf2.length());
+  EXPECT_EQ(256U, buf2.capacity());  // capacity does transfer
+  EXPECT_EQ(0, memcmp(buf2.data(), kTestData, sizeof(kTestData)));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/checks.h b/talk/base/checks.h
index 12adc25..83ad372 100644
--- a/talk/base/checks.h
+++ b/talk/base/checks.h
@@ -26,10 +26,10 @@
  */
 
 // This module contains some basic debugging facilities.
+// Originally comes from shared/commandlineflags/checks.h
 
-
-#ifndef SHARED_COMMANDLINEFLAGS_CHECKS_H__
-#define SHARED_COMMANDLINEFLAGS_CHECKS_H__
+#ifndef TALK_BASE_CHECKS_H_
+#define TALK_BASE_CHECKS_H_
 
 #include <string.h>
 
@@ -41,4 +41,4 @@
 #define UNREACHABLE()                                   \
   Fatal(__FILE__, __LINE__, "unreachable code")
 
-#endif  // SHARED_COMMANDLINEFLAGS_CHECKS_H__
+#endif  // TALK_BASE_CHECKS_H_
diff --git a/talk/base/cpuid.cc b/talk/base/cpuid.cc
new file mode 100644
index 0000000..7f0326a
--- /dev/null
+++ b/talk/base/cpuid.cc
@@ -0,0 +1,140 @@
+/*
+ * libjingle
+ * Copyright 2011 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/cpuid.h"
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#elif defined(__ANDROID__)
+#include <cpu-features.h>
+#elif defined(LINUX)
+#include "talk/base/linux.h"
+#endif
+
+// TODO: Use cpuid.h when gcc 4.4 is used on OSX and Linux.
+#if (defined(__pic__) || defined(__APPLE__)) && defined(__i386__)
+static inline void __cpuid(int cpu_info[4], int info_type) {
+  __asm__ volatile (
+    "mov %%ebx, %%edi\n"
+    "cpuid\n"
+    "xchg %%edi, %%ebx\n"
+    : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3])
+    : "a"(info_type)
+  );
+}
+#elif defined(__i386__) || defined(__x86_64__)
+static inline void __cpuid(int cpu_info[4], int info_type) {
+  __asm__ volatile (
+    "cpuid\n"
+    : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3])
+    : "a"(info_type)
+  );
+}
+#endif
+
+namespace talk_base {
+
+// CPU detect function for SIMD instruction sets.
+bool CpuInfo::cpu_info_initialized_ = false;
+int CpuInfo::cpu_info_ = 0;
+// Global lock for cpu initialization.
+CriticalSection CpuInfo::crit_;
+
+#ifdef CPU_X86
+void cpuid(int cpu_info[4], int info_type) {
+  __cpuid(cpu_info, info_type);
+}
+#endif
+
+void CpuInfo::InitCpuFlags() {
+#ifdef CPU_X86
+  int cpu_info[4];
+  __cpuid(cpu_info, 1);
+  cpu_info_ = (cpu_info[2] & 0x00000200 ? kCpuHasSSSE3 : 0) |
+    (cpu_info[3] & 0x04000000 ? kCpuHasSSE2 : 0);
+#elif defined(__ANDROID__) && defined(__arm__)
+  uint64_t features = android_getCpuFeatures();
+  cpu_info_ = ((features & ANDROID_CPU_ARM_FEATURE_NEON) ? kCpuHasNEON : 0);
+#elif defined(LINUX) && defined(__arm__)
+  cpu_info_ = 0;
+  // Look for NEON support in /proc/cpuinfo
+  ProcCpuInfo proc_info;
+  size_t section_count;
+  if (proc_info.LoadFromSystem() &&
+      proc_info.GetSectionCount(&section_count)) {
+    for (size_t i = 0; i < section_count; ++i) {
+      std::string out_features;
+      if (proc_info.GetSectionStringValue(i, "Features", &out_features)) {
+        if (out_features.find("neon") != std::string::npos) {
+          cpu_info_ |= kCpuHasNEON;
+        }
+        break;
+      }
+    }
+  }
+#elif defined(__ARM_NEON__)
+  // gcc -mfpu=neon defines __ARM_NEON__
+  // if code is specifically built for Neon-only, enable the flag.
+  cpu_info_ |= kCpuHasNEON;
+#else
+  cpu_info_ = 0;
+#endif
+  cpu_info_initialized_ = true;
+}
+
+void CpuInfo::MaskCpuFlagsForTest(int enable_flags) {
+  CritScope cs(&crit_);
+  InitCpuFlags();
+  cpu_info_ &= enable_flags;
+}
+
+bool CpuInfo::TestCpuFlag(int flag) {
+  if (!cpu_info_initialized_) {
+    CritScope cs(&crit_);
+    InitCpuFlags();
+  }
+  return cpu_info_ & flag ? true : false;
+}
+
+// Returns the vendor string from the cpu, e.g. "GenuineIntel", "AuthenticAMD".
+// See "Intel Processor Identification and the CPUID Instruction"
+// (Intel document number: 241618)
+std::string CpuInfo::GetCpuVendor() {
+#ifdef CPU_X86
+  int cpu_info[4];
+  cpuid(cpu_info, 0);
+  cpu_info[0] = cpu_info[1];  // Reorder output
+  cpu_info[1] = cpu_info[3];
+  cpu_info[2] = cpu_info[2];
+  cpu_info[3] = 0;
+  return std::string(reinterpret_cast<char *>(&cpu_info[0]));
+#else
+  return std::string("Undefined");
+#endif
+}
+
+}  // namespace talk_base
diff --git a/talk/base/cpuid.h b/talk/base/cpuid.h
new file mode 100644
index 0000000..3688668
--- /dev/null
+++ b/talk/base/cpuid.h
@@ -0,0 +1,73 @@
+/*
+ * libjingle
+ * Copyright 2011 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_CPUID_H_
+#define TALK_BASE_CPUID_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+#ifdef CPU_X86
+void cpuid(int cpu_info[4], int info_type);
+#endif
+
+class CpuInfo {
+ public:
+  // These flags are only valid on x86 processors
+  static const int kCpuHasSSE2 = 1;
+  static const int kCpuHasSSSE3 = 2;
+
+  // SIMD support on ARM processors
+  static const int kCpuHasNEON = 4;
+
+  // Detect CPU has SSE2 etc.
+  static bool TestCpuFlag(int flag);
+
+  // Detect CPU vendor: "GenuineIntel" or "AuthenticAMD"
+  static std::string GetCpuVendor();
+
+  // For testing, allow CPU flags to be disabled.
+  static void MaskCpuFlagsForTest(int enable_flags);
+
+ private:
+  // Global lock for the cpu initialization
+  static CriticalSection crit_;
+  static bool cpu_info_initialized_;
+  static int cpu_info_;
+
+  static void InitCpuFlags();
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(CpuInfo);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_CPUID_H_
diff --git a/talk/base/cpuid_unittest.cc b/talk/base/cpuid_unittest.cc
new file mode 100644
index 0000000..f06178f
--- /dev/null
+++ b/talk/base/cpuid_unittest.cc
@@ -0,0 +1,86 @@
+/*
+ * 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 <iostream>
+
+#include "talk/base/cpuid.h"
+#include "talk/base/gunit.h"
+#include "talk/base/stringutils.h"
+
+
+// Tests CPUID instruction for Vendor identification.
+TEST(CpuInfoTest, CpuVendorNonEmpty) {
+  EXPECT_FALSE(talk_base::CpuInfo::GetCpuVendor().empty());
+}
+
+#ifdef CPU_X86
+
+// Tests Vendor identification is Intel or AMD.
+// See Also http://en.wikipedia.org/wiki/CPUID
+TEST(CpuInfoTest, CpuVendorIntelAMD) {
+  const std::string vendor = talk_base::CpuInfo::GetCpuVendor();
+  LOG(LS_INFO) << "CpuVendor: " << vendor;
+  EXPECT_TRUE(talk_base::string_match(vendor.c_str(),
+                                      "GenuineIntel") ||
+              talk_base::string_match(vendor.c_str(),
+                                      "AuthenticAMD"));
+}
+
+// Tests CPUID maximum function number.
+// Modern CPU has 11, but we expect at least 3.
+TEST(CpuInfoTest, CpuIdMax) {
+  int cpu_info[4] = { 0 };
+  talk_base::cpuid(cpu_info, 0);
+  LOG(LS_INFO) << "CpuId Max Function: " << cpu_info[0];
+  EXPECT_GE(cpu_info[0], 1);
+}
+
+// Tests CPUID functions 0 and 1 return different values.
+TEST(CpuInfoTest, CpuId) {
+  int cpu_info0[4] = { 0 };
+  int cpu_info1[4] = { 0 };
+  talk_base::cpuid(cpu_info0, 0);
+  talk_base::cpuid(cpu_info1, 1);
+  LOG(LS_INFO) << "CpuId Function 0: " << std::hex
+               << std::setfill('0') << std::setw(8) << cpu_info0[0] << " "
+               << std::setfill('0') << std::setw(8) << cpu_info0[1] << " "
+               << std::setfill('0') << std::setw(8) << cpu_info0[2] << " "
+               << std::setfill('0') << std::setw(8) << cpu_info0[3];
+  LOG(LS_INFO) << "CpuId Function 1: " << std::hex
+               << std::setfill('0') << std::setw(8) << cpu_info1[0] << " "
+               << std::setfill('0') << std::setw(8) << cpu_info1[1] << " "
+               << std::setfill('0') << std::setw(8) << cpu_info1[2] << " "
+               << std::setfill('0') << std::setw(8) << cpu_info1[3];
+  EXPECT_NE(memcmp(cpu_info0, cpu_info1, sizeof(cpu_info0)), 0);
+
+  LOG(LS_INFO) << "SSE2: "
+      << talk_base::CpuInfo::TestCpuFlag(talk_base::CpuInfo::kCpuHasSSE2);
+  LOG(LS_INFO) << "SSSE3: "
+      << talk_base::CpuInfo::TestCpuFlag(talk_base::CpuInfo::kCpuHasSSSE3);
+}
+#endif
+
diff --git a/talk/base/cpumonitor.cc b/talk/base/cpumonitor.cc
new file mode 100644
index 0000000..64f6147
--- /dev/null
+++ b/talk/base/cpumonitor.cc
@@ -0,0 +1,418 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/cpumonitor.h"
+
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/systeminfo.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <winternl.h>
+#endif
+
+#ifdef POSIX
+#include <sys/time.h>
+#endif
+
+#if defined(IOS) || defined(OSX)
+#include <mach/mach_host.h>
+#include <mach/mach_init.h>
+#include <mach/host_info.h>
+#include <mach/task.h>
+#endif  // defined(IOS) || defined(OSX)
+
+#if defined(LINUX) || defined(ANDROID)
+#include <sys/resource.h>
+#include <errno.h>
+#include <stdio.h>
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#endif // defined(LINUX) || defined(ANDROID)
+
+#if defined(IOS) || defined(OSX)
+static uint64 TimeValueTToInt64(const time_value_t &time_value) {
+  return talk_base::kNumMicrosecsPerSec * time_value.seconds +
+      time_value.microseconds;
+}
+#endif  // defined(IOS) || defined(OSX)
+
+// How CpuSampler works
+// When threads switch, the time they spent is accumulated to system counters.
+// The time can be treated as user, kernel or idle.
+// user time is applications.
+// kernel time is the OS, including the thread switching code itself.
+//   typically kernel time indicates IO.
+// idle time is a process that wastes time when nothing is ready to run.
+//
+// User time is broken down by process (application).  One of the applications
+// is the current process.  When you add up all application times, this is
+// system time.  If only your application is running, system time should be the
+// same as process time.
+//
+// All cores contribute to these accumulators.  A dual core process is able to
+// process twice as many cycles as a single core.  The actual code efficiency
+// may be worse, due to contention, but the available cycles is exactly twice
+// as many, and the cpu load will reflect the efficiency.  Hyperthreads behave
+// the same way.  The load will reflect 200%, but the actual amount of work
+// completed will be much less than a true dual core.
+//
+// Total available performance is the sum of all accumulators.
+// If you tracked this for 1 second, it would essentially give you the clock
+// rate - number of cycles per second.
+// Speed step / Turbo Boost is not considered, so infact more processing time
+// may be available.
+
+namespace talk_base {
+
+// Note Tests on Windows show 600 ms is minimum stable interval for Windows 7.
+static const int32 kDefaultInterval = 950;  // Slightly under 1 second.
+
+CpuSampler::CpuSampler()
+    : min_load_interval_(kDefaultInterval)
+#ifdef WIN32
+      , get_system_times_(NULL),
+      nt_query_system_information_(NULL),
+      force_fallback_(false)
+#endif
+    {
+}
+
+CpuSampler::~CpuSampler() {
+}
+
+// Set minimum interval in ms between computing new load values. Default 950.
+void CpuSampler::set_load_interval(int min_load_interval) {
+  min_load_interval_ = min_load_interval;
+}
+
+bool CpuSampler::Init() {
+  sysinfo_.reset(new SystemInfo);
+  cpus_ = sysinfo_->GetMaxCpus();
+  if (cpus_ == 0) {
+    return false;
+  }
+#ifdef WIN32
+  // Note that GetSystemTimes is available in Windows XP SP1 or later.
+  // http://msdn.microsoft.com/en-us/library/ms724400.aspx
+  // NtQuerySystemInformation is used as a fallback.
+  if (!force_fallback_) {
+    get_system_times_ = GetProcAddress(GetModuleHandle(L"kernel32.dll"),
+        "GetSystemTimes");
+  }
+  nt_query_system_information_ = GetProcAddress(GetModuleHandle(L"ntdll.dll"),
+      "NtQuerySystemInformation");
+  if ((get_system_times_ == NULL) && (nt_query_system_information_ == NULL)) {
+    return false;
+  }
+#endif
+#if defined(LINUX) || defined(ANDROID)
+  Pathname sname("/proc/stat");
+  sfile_.reset(Filesystem::OpenFile(sname, "rb"));
+  if (sfile_.get() == NULL) {
+    LOG_ERR(LS_ERROR) << "open proc/stat failed:";
+    return false;
+  }
+  if (!sfile_->DisableBuffering()) {
+    LOG_ERR(LS_ERROR) << "could not disable buffering for proc/stat";
+    return false;
+  }
+#endif // defined(LINUX) || defined(ANDROID)
+  GetProcessLoad();  // Initialize values.
+  GetSystemLoad();
+  // Help next user call return valid data by recomputing load.
+  process_.prev_load_time_ = 0u;
+  system_.prev_load_time_ = 0u;
+  return true;
+}
+
+float CpuSampler::UpdateCpuLoad(uint64 current_total_times,
+                                uint64 current_cpu_times,
+                                uint64 *prev_total_times,
+                                uint64 *prev_cpu_times) {
+  float result = 0.f;
+  if (current_total_times < *prev_total_times ||
+      current_cpu_times < *prev_cpu_times) {
+    LOG(LS_ERROR) << "Inconsistent time values are passed. ignored";
+  } else {
+    const uint64 cpu_diff = current_cpu_times - *prev_cpu_times;
+    const uint64 total_diff = current_total_times - *prev_total_times;
+    result = (total_diff == 0ULL ? 0.f :
+              static_cast<float>(1.0f * cpu_diff / total_diff));
+    if (result > static_cast<float>(cpus_)) {
+      result = static_cast<float>(cpus_);
+    }
+    *prev_total_times = current_total_times;
+    *prev_cpu_times = current_cpu_times;
+  }
+  return result;
+}
+
+float CpuSampler::GetSystemLoad() {
+  uint32 timenow = Time();
+  int elapsed = static_cast<int>(TimeDiff(timenow, system_.prev_load_time_));
+  if (min_load_interval_ != 0 && system_.prev_load_time_ != 0u &&
+      elapsed < min_load_interval_) {
+    return system_.prev_load_;
+  }
+#ifdef WIN32
+  uint64 total_times, cpu_times;
+
+  typedef BOOL (_stdcall *GST_PROC)(LPFILETIME, LPFILETIME, LPFILETIME);
+  typedef NTSTATUS (WINAPI *QSI_PROC)(SYSTEM_INFORMATION_CLASS,
+      PVOID, ULONG, PULONG);
+
+  GST_PROC get_system_times = reinterpret_cast<GST_PROC>(get_system_times_);
+  QSI_PROC nt_query_system_information = reinterpret_cast<QSI_PROC>(
+      nt_query_system_information_);
+
+  if (get_system_times) {
+    FILETIME idle_time, kernel_time, user_time;
+    if (!get_system_times(&idle_time, &kernel_time, &user_time)) {
+      LOG(LS_ERROR) << "::GetSystemTimes() failed: " << ::GetLastError();
+      return 0.f;
+    }
+    // kernel_time includes Kernel idle time, so no need to
+    // include cpu_time as total_times
+    total_times = ToUInt64(kernel_time) + ToUInt64(user_time);
+    cpu_times = total_times - ToUInt64(idle_time);
+
+  } else {
+    if (nt_query_system_information) {
+      ULONG returned_length = 0;
+      scoped_array<SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION> processor_info(
+          new SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[cpus_]);
+      nt_query_system_information(
+          ::SystemProcessorPerformanceInformation,
+          reinterpret_cast<void*>(processor_info.get()),
+          cpus_ * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION),
+          &returned_length);
+
+      if (returned_length !=
+          (cpus_ * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION))) {
+        LOG(LS_ERROR) << "NtQuerySystemInformation has unexpected size";
+        return 0.f;
+      }
+
+      uint64 current_idle = 0;
+      uint64 current_kernel = 0;
+      uint64 current_user = 0;
+      for (int ix = 0; ix < cpus_; ++ix) {
+        current_idle += processor_info[ix].IdleTime.QuadPart;
+        current_kernel += processor_info[ix].UserTime.QuadPart;
+        current_user += processor_info[ix].KernelTime.QuadPart;
+      }
+      total_times = current_kernel + current_user;
+      cpu_times = total_times - current_idle;
+    } else {
+      return 0.f;
+    }
+  }
+#endif  // WIN32
+
+#if defined(IOS) || defined(OSX)
+  host_cpu_load_info_data_t cpu_info;
+  mach_msg_type_number_t info_count = HOST_CPU_LOAD_INFO_COUNT;
+  if (KERN_SUCCESS != host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO,
+                                      reinterpret_cast<host_info_t>(&cpu_info),
+                                      &info_count)) {
+    LOG(LS_ERROR) << "::host_statistics() failed";
+    return 0.f;
+  }
+
+  const uint64 cpu_times = cpu_info.cpu_ticks[CPU_STATE_NICE] +
+      cpu_info.cpu_ticks[CPU_STATE_SYSTEM] +
+      cpu_info.cpu_ticks[CPU_STATE_USER];
+  const uint64 total_times = cpu_times + cpu_info.cpu_ticks[CPU_STATE_IDLE];
+#endif  // defined(IOS) || defined(OSX)
+
+#if defined(LINUX) || defined(ANDROID)
+  if (sfile_.get() == NULL) {
+    LOG(LS_ERROR) << "Invalid handle for proc/stat";
+    return 0.f;
+  }
+  std::string statbuf;
+  sfile_->SetPosition(0);
+  if (!sfile_->ReadLine(&statbuf)) {
+    LOG_ERR(LS_ERROR) << "Could not read proc/stat file";
+    return 0.f;
+  }
+
+  unsigned long long user;
+  unsigned long long nice;
+  unsigned long long system;
+  unsigned long long idle;
+  if (sscanf(statbuf.c_str(), "cpu %Lu %Lu %Lu %Lu",
+             &user, &nice,
+             &system, &idle) != 4) {
+    LOG_ERR(LS_ERROR) << "Could not parse cpu info";
+    return 0.f;
+  }
+  const uint64 cpu_times = nice + system + user;
+  const uint64 total_times = cpu_times + idle;
+#endif  // defined(LINUX) || defined(ANDROID)
+  system_.prev_load_time_ = timenow;
+  system_.prev_load_ = UpdateCpuLoad(total_times,
+                                     cpu_times * cpus_,
+                                     &system_.prev_total_times_,
+                                     &system_.prev_cpu_times_);
+  return system_.prev_load_;
+}
+
+float CpuSampler::GetProcessLoad() {
+  uint32 timenow = Time();
+  int elapsed = static_cast<int>(TimeDiff(timenow, process_.prev_load_time_));
+  if (min_load_interval_ != 0 && process_.prev_load_time_ != 0u &&
+      elapsed < min_load_interval_) {
+    return process_.prev_load_;
+  }
+#ifdef WIN32
+  FILETIME current_file_time;
+  ::GetSystemTimeAsFileTime(&current_file_time);
+
+  FILETIME create_time, exit_time, kernel_time, user_time;
+  if (!::GetProcessTimes(::GetCurrentProcess(),
+                         &create_time, &exit_time, &kernel_time, &user_time)) {
+    LOG(LS_ERROR) << "::GetProcessTimes() failed: " << ::GetLastError();
+    return 0.f;
+  }
+
+  const uint64 total_times =
+      ToUInt64(current_file_time) - ToUInt64(create_time);
+  const uint64 cpu_times =
+      (ToUInt64(kernel_time) + ToUInt64(user_time));
+#endif  // WIN32
+
+#ifdef POSIX
+  // Common to both OSX and Linux.
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+  const uint64 total_times = tv.tv_sec * kNumMicrosecsPerSec + tv.tv_usec;
+#endif
+
+#if defined(IOS) || defined(OSX)
+  // Get live thread usage.
+  task_thread_times_info task_times_info;
+  mach_msg_type_number_t info_count = TASK_THREAD_TIMES_INFO_COUNT;
+
+  if (KERN_SUCCESS != task_info(mach_task_self(), TASK_THREAD_TIMES_INFO,
+                                reinterpret_cast<task_info_t>(&task_times_info),
+                                &info_count)) {
+    LOG(LS_ERROR) << "::task_info(TASK_THREAD_TIMES_INFO) failed";
+    return 0.f;
+  }
+
+  // Get terminated thread usage.
+  task_basic_info task_term_info;
+  info_count = TASK_BASIC_INFO_COUNT;
+  if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO,
+                                reinterpret_cast<task_info_t>(&task_term_info),
+                                &info_count)) {
+    LOG(LS_ERROR) << "::task_info(TASK_BASIC_INFO) failed";
+    return 0.f;
+  }
+
+  const uint64 cpu_times = (TimeValueTToInt64(task_times_info.user_time) +
+      TimeValueTToInt64(task_times_info.system_time) +
+      TimeValueTToInt64(task_term_info.user_time) +
+      TimeValueTToInt64(task_term_info.system_time));
+#endif  // defined(IOS) || defined(OSX)
+
+#if defined(LINUX) || defined(ANDROID)
+  rusage usage;
+  if (getrusage(RUSAGE_SELF, &usage) < 0) {
+    LOG_ERR(LS_ERROR) << "getrusage failed";
+    return 0.f;
+  }
+
+  const uint64 cpu_times =
+      (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * kNumMicrosecsPerSec +
+      usage.ru_utime.tv_usec + usage.ru_stime.tv_usec;
+#endif  // defined(LINUX) || defined(ANDROID)
+  process_.prev_load_time_ = timenow;
+  process_.prev_load_ = UpdateCpuLoad(total_times,
+                                     cpu_times,
+                                     &process_.prev_total_times_,
+                                     &process_.prev_cpu_times_);
+  return process_.prev_load_;
+}
+
+int CpuSampler::GetMaxCpus() const {
+  return cpus_;
+}
+
+int CpuSampler::GetCurrentCpus() {
+  return sysinfo_->GetCurCpus();
+}
+
+///////////////////////////////////////////////////////////////////
+// Implementation of class CpuMonitor.
+CpuMonitor::CpuMonitor(Thread* thread)
+    : monitor_thread_(thread ? thread : Thread::Current()) {
+  monitor_thread_->SignalQueueDestroyed.connect(
+      this, &CpuMonitor::OnMessageQueueDestroyed);
+}
+
+CpuMonitor::~CpuMonitor() {
+  Stop();
+}
+
+bool CpuMonitor::Start(int period_ms) {
+  if (!sampler_.Init()) return false;
+
+  period_ms_ = period_ms;
+  if (monitor_thread_) {
+    monitor_thread_->PostDelayed(period_ms_, this);
+  }
+  return true;
+}
+
+void CpuMonitor::Stop() {
+  if (monitor_thread_) {
+    monitor_thread_->Clear(this);
+  }
+}
+
+void CpuMonitor::OnMessage(Message* msg) {
+  int max_cpus = sampler_.GetMaxCpus();
+  int current_cpus = sampler_.GetCurrentCpus();
+  float process_load = sampler_.GetProcessLoad();
+  float system_load = sampler_.GetSystemLoad();
+  SignalUpdate(current_cpus, max_cpus, process_load, system_load);
+
+  if (monitor_thread_) {
+    monitor_thread_->PostDelayed(period_ms_, this);
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/cpumonitor.h b/talk/base/cpumonitor.h
new file mode 100644
index 0000000..f633f50
--- /dev/null
+++ b/talk/base/cpumonitor.h
@@ -0,0 +1,139 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_CPUMONITOR_H_
+#define TALK_BASE_CPUMONITOR_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#if defined(LINUX) || defined(ANDROID)
+#include "talk/base/stream.h"
+#endif // defined(LINUX) || defined(ANDROID)
+
+namespace talk_base {
+class Thread;
+class SystemInfo;
+
+struct CpuStats {
+  CpuStats()
+      : prev_total_times_(0),
+        prev_cpu_times_(0),
+        prev_load_(0.f),
+        prev_load_time_(0u) {
+  }
+
+  uint64 prev_total_times_;
+  uint64 prev_cpu_times_;
+  float prev_load_;  // Previous load value.
+  uint32 prev_load_time_;  // Time previous load value was taken.
+};
+
+// CpuSampler samples the process and system load.
+class CpuSampler {
+ public:
+  CpuSampler();
+  ~CpuSampler();
+
+  // Initialize CpuSampler.  Returns true if successful.
+  bool Init();
+
+  // Set minimum interval in ms between computing new load values.
+  // Default 950 ms.  Set to 0 to disable interval.
+  void set_load_interval(int min_load_interval);
+
+  // Return CPU load of current process as a float from 0 to 1.
+  float GetProcessLoad();
+
+  // Return CPU load of current process as a float from 0 to 1.
+  float GetSystemLoad();
+
+  // Return number of cpus. Includes hyperthreads.
+  int GetMaxCpus() const;
+
+  // Return current number of cpus available to this process.
+  int GetCurrentCpus();
+
+  // For testing. Allows forcing of fallback to using NTDLL functions.
+  void set_force_fallback(bool fallback) {
+#ifdef WIN32
+    force_fallback_ = fallback;
+#endif
+  }
+
+ private:
+  float UpdateCpuLoad(uint64 current_total_times,
+                      uint64 current_cpu_times,
+                      uint64 *prev_total_times,
+                      uint64 *prev_cpu_times);
+  CpuStats process_;
+  CpuStats system_;
+  int cpus_;
+  int min_load_interval_;  // Minimum time between computing new load.
+  scoped_ptr<SystemInfo> sysinfo_;
+#ifdef WIN32
+  void* get_system_times_;
+  void* nt_query_system_information_;
+  bool force_fallback_;
+#endif
+#if defined(LINUX) || defined(ANDROID)
+  // File for reading /proc/stat
+  scoped_ptr<FileStream> sfile_;
+#endif // defined(LINUX) || defined(ANDROID)
+};
+
+// CpuMonitor samples and signals the CPU load periodically.
+class CpuMonitor
+    : public talk_base::MessageHandler, public sigslot::has_slots<> {
+ public:
+  explicit CpuMonitor(Thread* thread);
+  virtual ~CpuMonitor();
+
+  bool Start(int period_ms);
+  void Stop();
+  // Signal parameters are current cpus, max cpus, process load and system load.
+  sigslot::signal4<int, int, float, float> SignalUpdate;
+
+ protected:
+  // Override virtual method of parent MessageHandler.
+  virtual void OnMessage(talk_base::Message* msg);
+  // Clear the monitor thread and stop sending it messages if the thread goes
+  // away before our lifetime.
+  void OnMessageQueueDestroyed() { monitor_thread_ = NULL; }
+
+ private:
+  Thread* monitor_thread_;
+  CpuSampler sampler_;
+  int period_ms_;
+
+  DISALLOW_COPY_AND_ASSIGN(CpuMonitor);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_CPUMONITOR_H_
diff --git a/talk/base/cpumonitor_unittest.cc b/talk/base/cpumonitor_unittest.cc
new file mode 100644
index 0000000..e83c969
--- /dev/null
+++ b/talk/base/cpumonitor_unittest.cc
@@ -0,0 +1,401 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iomanip>
+#include <iostream>
+#include <vector>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/cpumonitor.h"
+#include "talk/base/flags.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/base/timing.h"
+
+namespace talk_base {
+
+static const int kMaxCpus = 1024;
+static const int kSettleTime = 100;  // Amount of time to between tests.
+static const int kIdleTime = 500;  // Amount of time to be idle in ms.
+static const int kBusyTime = 1000;  // Amount of time to be busy in ms.
+
+class BusyThread : public talk_base::Thread {
+ public:
+  BusyThread(double load, double duration, double interval) :
+    load_(load), duration_(duration), interval_(interval) {
+  }
+  void Run() {
+    Timing time;
+    double busy_time = interval_ * load_ / 100.0;
+    for (;;) {
+      time.BusyWait(busy_time);
+      time.IdleWait(interval_ - busy_time);
+      if (duration_) {
+        duration_ -= interval_;
+        if (duration_ <= 0) {
+          break;
+        }
+      }
+    }
+  }
+ private:
+  double load_;
+  double duration_;
+  double interval_;
+};
+
+class CpuLoadListener : public sigslot::has_slots<> {
+ public:
+  CpuLoadListener()
+      : current_cpus_(0),
+        cpus_(0),
+        process_load_(.0f),
+        system_load_(.0f),
+        count_(0) {
+  }
+
+  void OnCpuLoad(int current_cpus, int cpus, float proc_load, float sys_load) {
+    current_cpus_ = current_cpus;
+    cpus_ = cpus;
+    process_load_ = proc_load;
+    system_load_ = sys_load;
+    ++count_;
+  }
+
+  int current_cpus() const { return current_cpus_; }
+  int cpus() const { return cpus_; }
+  float process_load() const { return process_load_; }
+  float system_load() const { return system_load_; }
+  int count() const { return count_; }
+
+ private:
+  int current_cpus_;
+  int cpus_;
+  float process_load_;
+  float system_load_;
+  int count_;
+};
+
+// Set affinity (which cpu to run on), but respecting FLAG_affinity:
+// -1 means no affinity - run on whatever cpu is available.
+// 0 .. N means run on specific cpu.  The tool will create N threads and call
+//   SetThreadAffinity on 0 to N - 1 as cpu.  FLAG_affinity sets the first cpu
+//   so the range becomes affinity to affinity + N - 1
+// Note that this function affects Windows scheduling, effectively giving
+//   the thread with affinity for a specified CPU more priority on that CPU.
+bool SetThreadAffinity(BusyThread* t, int cpu, int affinity) {
+#ifdef WIN32
+  if (affinity >= 0) {
+    return ::SetThreadAffinityMask(t->GetHandle(),
+        1 << (cpu + affinity)) != FALSE;
+  }
+#endif
+  return true;
+}
+
+bool SetThreadPriority(BusyThread* t, int prio) {
+  if (!prio) {
+    return true;
+  }
+  bool ok = t->SetPriority(static_cast<talk_base::ThreadPriority>(prio));
+  if (!ok) {
+    std::cout << "Error setting thread priority." << std::endl;
+  }
+  return ok;
+}
+
+int CpuLoad(double cpuload, double duration, int numthreads,
+            int priority, double interval, int affinity) {
+  int ret = 0;
+  std::vector<BusyThread*> threads;
+  for (int i = 0; i < numthreads; ++i) {
+    threads.push_back(new BusyThread(cpuload, duration, interval));
+    // NOTE(fbarchard): Priority must be done before Start.
+    if (!SetThreadPriority(threads[i], priority) ||
+       !threads[i]->Start() ||
+       !SetThreadAffinity(threads[i], i, affinity)) {
+      ret = 1;
+      break;
+    }
+  }
+  // Wait on each thread
+  if (ret == 0) {
+    for (int i = 0; i < numthreads; ++i) {
+      threads[i]->Stop();
+    }
+  }
+
+  for (int i = 0; i < numthreads; ++i) {
+    delete threads[i];
+  }
+  return ret;
+}
+
+// Make 2 CPUs busy
+static void CpuTwoBusyLoop(int busytime) {
+  CpuLoad(100.0, busytime / 1000.0, 2, 1, 0.050, -1);
+}
+
+// Make 1 CPUs busy
+static void CpuBusyLoop(int busytime) {
+  CpuLoad(100.0, busytime / 1000.0, 1, 1, 0.050, -1);
+}
+
+// Make 1 use half CPU time.
+static void CpuHalfBusyLoop(int busytime) {
+  CpuLoad(50.0, busytime / 1000.0, 1, 1, 0.050, -1);
+}
+
+void TestCpuSampler(bool test_proc, bool test_sys, bool force_fallback) {
+  CpuSampler sampler;
+  sampler.set_force_fallback(force_fallback);
+  EXPECT_TRUE(sampler.Init());
+  sampler.set_load_interval(100);
+  int cpus = sampler.GetMaxCpus();
+
+  // Test1: CpuSampler under idle situation.
+  Thread::SleepMs(kSettleTime);
+  sampler.GetProcessLoad();
+  sampler.GetSystemLoad();
+
+  Thread::SleepMs(kIdleTime);
+
+  float proc_idle = 0.f, sys_idle = 0.f;
+  if (test_proc) {
+    proc_idle = sampler.GetProcessLoad();
+  }
+  if (test_sys) {
+      sys_idle = sampler.GetSystemLoad();
+  }
+  if (test_proc) {
+    LOG(LS_INFO) << "ProcessLoad Idle:      "
+                 << setiosflags(std::ios_base::fixed)
+                 << std::setprecision(2) << std::setw(6) << proc_idle;
+    EXPECT_GE(proc_idle, 0.f);
+    EXPECT_LE(proc_idle, static_cast<float>(cpus));
+  }
+  if (test_sys) {
+    LOG(LS_INFO) << "SystemLoad Idle:       "
+                 << setiosflags(std::ios_base::fixed)
+                 << std::setprecision(2) << std::setw(6) << sys_idle;
+    EXPECT_GE(sys_idle, 0.f);
+    EXPECT_LE(sys_idle, static_cast<float>(cpus));
+  }
+
+  // Test2: CpuSampler with main process at 50% busy.
+  Thread::SleepMs(kSettleTime);
+  sampler.GetProcessLoad();
+  sampler.GetSystemLoad();
+
+  CpuHalfBusyLoop(kBusyTime);
+
+  float proc_halfbusy = 0.f, sys_halfbusy = 0.f;
+  if (test_proc) {
+    proc_halfbusy = sampler.GetProcessLoad();
+  }
+  if (test_sys) {
+    sys_halfbusy = sampler.GetSystemLoad();
+  }
+  if (test_proc) {
+    LOG(LS_INFO) << "ProcessLoad Halfbusy:  "
+                 << setiosflags(std::ios_base::fixed)
+                 << std::setprecision(2) << std::setw(6) << proc_halfbusy;
+    EXPECT_GE(proc_halfbusy, 0.f);
+    EXPECT_LE(proc_halfbusy, static_cast<float>(cpus));
+  }
+  if (test_sys) {
+    LOG(LS_INFO) << "SystemLoad Halfbusy:   "
+                 << setiosflags(std::ios_base::fixed)
+                 << std::setprecision(2) << std::setw(6) << sys_halfbusy;
+    EXPECT_GE(sys_halfbusy, 0.f);
+    EXPECT_LE(sys_halfbusy, static_cast<float>(cpus));
+  }
+
+  // Test3: CpuSampler with main process busy.
+  Thread::SleepMs(kSettleTime);
+  sampler.GetProcessLoad();
+  sampler.GetSystemLoad();
+
+  CpuBusyLoop(kBusyTime);
+
+  float proc_busy = 0.f, sys_busy = 0.f;
+  if (test_proc) {
+    proc_busy = sampler.GetProcessLoad();
+  }
+  if (test_sys) {
+    sys_busy = sampler.GetSystemLoad();
+  }
+  if (test_proc) {
+    LOG(LS_INFO) << "ProcessLoad Busy:      "
+                 << setiosflags(std::ios_base::fixed)
+                 << std::setprecision(2) << std::setw(6) << proc_busy;
+    EXPECT_GE(proc_busy, 0.f);
+    EXPECT_LE(proc_busy, static_cast<float>(cpus));
+  }
+  if (test_sys) {
+    LOG(LS_INFO) << "SystemLoad Busy:       "
+                 << setiosflags(std::ios_base::fixed)
+                 << std::setprecision(2) << std::setw(6) << sys_busy;
+    EXPECT_GE(sys_busy, 0.f);
+    EXPECT_LE(sys_busy, static_cast<float>(cpus));
+  }
+
+  // Test4: CpuSampler with 2 cpus process busy.
+  if (cpus >= 2) {
+    Thread::SleepMs(kSettleTime);
+    sampler.GetProcessLoad();
+    sampler.GetSystemLoad();
+
+    CpuTwoBusyLoop(kBusyTime);
+
+    float proc_twobusy = 0.f, sys_twobusy = 0.f;
+    if (test_proc) {
+      proc_twobusy = sampler.GetProcessLoad();
+    }
+    if (test_sys) {
+      sys_twobusy = sampler.GetSystemLoad();
+    }
+    if (test_proc) {
+      LOG(LS_INFO) << "ProcessLoad 2 CPU Busy:"
+                   << setiosflags(std::ios_base::fixed)
+                   << std::setprecision(2) << std::setw(6) << proc_twobusy;
+      EXPECT_GE(proc_twobusy, 0.f);
+      EXPECT_LE(proc_twobusy, static_cast<float>(cpus));
+    }
+    if (test_sys) {
+      LOG(LS_INFO) << "SystemLoad 2 CPU Busy: "
+                   << setiosflags(std::ios_base::fixed)
+                   << std::setprecision(2) << std::setw(6) << sys_twobusy;
+      EXPECT_GE(sys_twobusy, 0.f);
+      EXPECT_LE(sys_twobusy, static_cast<float>(cpus));
+    }
+  }
+
+  // Test5: CpuSampler with idle process after being busy.
+  Thread::SleepMs(kSettleTime);
+  sampler.GetProcessLoad();
+  sampler.GetSystemLoad();
+
+  Thread::SleepMs(kIdleTime);
+
+  if (test_proc) {
+    proc_idle = sampler.GetProcessLoad();
+  }
+  if (test_sys) {
+    sys_idle = sampler.GetSystemLoad();
+  }
+  if (test_proc) {
+    LOG(LS_INFO) << "ProcessLoad Idle:      "
+                 << setiosflags(std::ios_base::fixed)
+                 << std::setprecision(2) << std::setw(6) << proc_idle;
+    EXPECT_GE(proc_idle, 0.f);
+    EXPECT_LE(proc_idle, proc_busy);
+  }
+  if (test_sys) {
+    LOG(LS_INFO) << "SystemLoad Idle:       "
+                 << setiosflags(std::ios_base::fixed)
+                 << std::setprecision(2) << std::setw(6) << sys_idle;
+    EXPECT_GE(sys_idle, 0.f);
+    EXPECT_LE(sys_idle, static_cast<float>(cpus));
+  }
+}
+
+TEST(CpuMonitorTest, TestCpus) {
+  CpuSampler sampler;
+  EXPECT_TRUE(sampler.Init());
+  int current_cpus = sampler.GetCurrentCpus();
+  int cpus = sampler.GetMaxCpus();
+  LOG(LS_INFO) << "Current Cpus:     " << std::setw(9) << current_cpus;
+  LOG(LS_INFO) << "Maximum Cpus:     " << std::setw(9) << cpus;
+  EXPECT_GT(cpus, 0);
+  EXPECT_LE(cpus, kMaxCpus);
+  EXPECT_GT(current_cpus, 0);
+  EXPECT_LE(current_cpus, cpus);
+}
+
+#ifdef WIN32
+// Tests overall system CpuSampler using legacy OS fallback code if applicable.
+TEST(CpuMonitorTest, TestGetSystemLoadForceFallback) {
+  TestCpuSampler(false, true, true);
+}
+#endif
+
+// Tests both process and system functions in use at same time.
+TEST(CpuMonitorTest, TestGetBothLoad) {
+  TestCpuSampler(true, true, false);
+}
+
+// Tests a query less than the interval produces the same value.
+TEST(CpuMonitorTest, TestInterval) {
+  CpuSampler sampler;
+  EXPECT_TRUE(sampler.Init());
+
+  // Test1: Interval of 500 ms
+  sampler.set_load_interval(500);
+
+  sampler.GetProcessLoad();
+  sampler.GetSystemLoad();
+
+  float proc_orig = sampler.GetProcessLoad();
+  float sys_orig = sampler.GetSystemLoad();
+
+  CpuBusyLoop(200);
+
+  float proc_halftime = sampler.GetProcessLoad();
+  float sys_halftime = sampler.GetSystemLoad();
+
+  EXPECT_EQ(proc_orig, proc_halftime);
+  EXPECT_EQ(sys_orig, sys_halftime);
+}
+
+TEST(CpuMonitorTest, TestCpuMonitor) {
+  CpuMonitor monitor(Thread::Current());
+  CpuLoadListener listener;
+  monitor.SignalUpdate.connect(&listener, &CpuLoadListener::OnCpuLoad);
+  EXPECT_TRUE(monitor.Start(10));
+  Thread::Current()->ProcessMessages(50);
+  EXPECT_GT(listener.count(), 2);  // We have checked cpu load more than twice.
+  EXPECT_GT(listener.current_cpus(), 0);
+  EXPECT_GT(listener.cpus(), 0);
+  EXPECT_GE(listener.process_load(), .0f);
+  EXPECT_GE(listener.system_load(), .0f);
+
+  monitor.Stop();
+  // Wait 20 ms to ake sure all signals are delivered.
+  Thread::Current()->ProcessMessages(20);
+  int old_count = listener.count();
+  Thread::Current()->ProcessMessages(20);
+  // Verfy no more siganls.
+  EXPECT_EQ(old_count, listener.count());
+}
+
+}  // namespace talk_base
diff --git a/talk/base/criticalsection.h b/talk/base/criticalsection.h
index fd0f06c..5a09a93 100644
--- a/talk/base/criticalsection.h
+++ b/talk/base/criticalsection.h
@@ -63,6 +63,13 @@
     EnterCriticalSection(&crit_);
     TRACK_OWNER(thread_ = GetCurrentThreadId());
   }
+  bool TryEnter() {
+    if (TryEnterCriticalSection(&crit_) != FALSE) {
+      TRACK_OWNER(thread_ = GetCurrentThreadId());
+      return true;
+    }
+    return false;
+  }
   void Leave() {
     TRACK_OWNER(thread_ = 0);
     LeaveCriticalSection(&crit_);
@@ -96,6 +103,13 @@
     pthread_mutex_lock(&mutex_);
     TRACK_OWNER(thread_ = pthread_self());
   }
+  bool TryEnter() {
+    if (pthread_mutex_trylock(&mutex_) == 0) {
+      TRACK_OWNER(thread_ = pthread_self());
+      return true;
+    }
+    return false;
+  }
   void Leave() {
     TRACK_OWNER(thread_ = 0);
     pthread_mutex_unlock(&mutex_);
diff --git a/talk/base/dbus.cc b/talk/base/dbus.cc
new file mode 100644
index 0000000..86fea4d
--- /dev/null
+++ b/talk/base/dbus.cc
@@ -0,0 +1,405 @@
+/*
+ * 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 HAVE_DBUS_GLIB
+
+#include "talk/base/dbus.h"
+
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// Avoid static object construction/destruction on startup/shutdown.
+static pthread_once_t g_dbus_init_once = PTHREAD_ONCE_INIT;
+static LibDBusGlibSymbolTable *g_dbus_symbol = NULL;
+
+// Releases DBus-Glib symbols.
+static void ReleaseDBusGlibSymbol() {
+  if (g_dbus_symbol != NULL) {
+    delete g_dbus_symbol;
+    g_dbus_symbol = NULL;
+  }
+}
+
+// Loads DBus-Glib symbols.
+static void InitializeDBusGlibSymbol() {
+  // This is thread safe.
+  if (NULL == g_dbus_symbol) {
+    g_dbus_symbol = new LibDBusGlibSymbolTable();
+
+    // Loads dbus-glib
+    if (NULL == g_dbus_symbol || !g_dbus_symbol->Load()) {
+      LOG(LS_WARNING) << "Failed to load dbus-glib symbol table.";
+      ReleaseDBusGlibSymbol();
+    } else {
+      // Nothing we can do if atexit() failed. Just ignore its returned value.
+      atexit(ReleaseDBusGlibSymbol);
+    }
+  }
+}
+
+// Returns a reference to the given late-binded symbol, with the correct type.
+#define LATE(sym) LATESYM_GET(LibDBusGlibSymbolTable, \
+    DBusMonitor::GetDBusGlibSymbolTable(), sym)
+
+// Implementation of class DBusSigMessageData
+DBusSigMessageData::DBusSigMessageData(DBusMessage *message)
+    : TypedMessageData<DBusMessage *>(message) {
+  LATE(dbus_message_ref)(data());
+}
+
+DBusSigMessageData::~DBusSigMessageData() {
+  LATE(dbus_message_unref)(data());
+}
+
+// Implementation of class DBusSigFilter
+
+// Builds a DBus filter string from given DBus path, interface and member.
+std::string DBusSigFilter::BuildFilterString(const std::string &path,
+                                             const std::string &interface,
+                                             const std::string &member) {
+  std::string ret(DBUS_TYPE "='" DBUS_SIGNAL "'");
+  if (!path.empty()) {
+    ret += ("," DBUS_PATH "='");
+    ret += path;
+    ret += "'";
+  }
+  if (!interface.empty()) {
+    ret += ("," DBUS_INTERFACE "='");
+    ret += interface;
+    ret += "'";
+  }
+  if (!member.empty()) {
+    ret += ("," DBUS_MEMBER "='");
+    ret += member;
+    ret += "'";
+  }
+  return ret;
+}
+
+// Forwards the message to the given instance.
+DBusHandlerResult DBusSigFilter::DBusCallback(DBusConnection *dbus_conn,
+                                              DBusMessage *message,
+                                              void *instance) {
+  ASSERT(instance);
+  if (instance) {
+    return static_cast<DBusSigFilter *>(instance)->Callback(message);
+  }
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+// Posts a message to caller thread.
+DBusHandlerResult DBusSigFilter::Callback(DBusMessage *message) {
+  if (caller_thread_) {
+    caller_thread_->Post(this, DSM_SIGNAL, new DBusSigMessageData(message));
+  }
+  // Don't "eat" the message here. Let it pop up.
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+// From MessageHandler.
+void DBusSigFilter::OnMessage(Message *message) {
+  if (message != NULL && DSM_SIGNAL == message->message_id) {
+    DBusSigMessageData *msg =
+        static_cast<DBusSigMessageData *>(message->pdata);
+    if (msg) {
+      ProcessSignal(msg->data());
+      delete msg;
+    }
+  }
+}
+
+// Definition of private class DBusMonitoringThread.
+// It creates a worker-thread to listen signals on DBus. The worker-thread will
+// be running in a priate GMainLoop forever until either Stop() has been invoked
+// or it hits an error.
+class DBusMonitor::DBusMonitoringThread : public talk_base::Thread {
+ public:
+  explicit DBusMonitoringThread(DBusMonitor *monitor,
+                                GMainContext *context,
+                                GMainLoop *mainloop,
+                                std::vector<DBusSigFilter *> *filter_list)
+      : monitor_(monitor),
+        context_(context),
+        mainloop_(mainloop),
+        connection_(NULL),
+        idle_source_(NULL),
+        filter_list_(filter_list) {
+    ASSERT(monitor_);
+    ASSERT(context_);
+    ASSERT(mainloop_);
+    ASSERT(filter_list_);
+  }
+
+  // Override virtual method of Thread. Context: worker-thread.
+  virtual void Run() {
+    ASSERT(NULL == connection_);
+
+    // Setup DBus connection and start monitoring.
+    monitor_->OnMonitoringStatusChanged(DMS_INITIALIZING);
+    if (!Setup()) {
+      LOG(LS_ERROR) << "DBus monitoring setup failed.";
+      monitor_->OnMonitoringStatusChanged(DMS_FAILED);
+      CleanUp();
+      return;
+    }
+    monitor_->OnMonitoringStatusChanged(DMS_RUNNING);
+    LATE(g_main_loop_run)(mainloop_);
+    monitor_->OnMonitoringStatusChanged(DMS_STOPPED);
+
+    // Done normally. Clean up DBus connection.
+    CleanUp();
+    return;
+  }
+
+  // Override virtual method of Thread. Context: caller-thread.
+  virtual void Stop() {
+    ASSERT(NULL == idle_source_);
+    // Add an idle source and let the gmainloop quit on idle.
+    idle_source_ = LATE(g_idle_source_new)();
+    if (idle_source_) {
+      LATE(g_source_set_callback)(idle_source_, &Idle, this, NULL);
+      LATE(g_source_attach)(idle_source_, context_);
+    } else {
+      LOG(LS_ERROR) << "g_idle_source_new() failed.";
+      QuitGMainloop();  // Try to quit anyway.
+    }
+
+    Thread::Stop();  // Wait for the thread.
+  }
+
+ private:
+  // Registers all DBus filters.
+  void RegisterAllFilters() {
+    ASSERT(NULL != LATE(dbus_g_connection_get_connection)(connection_));
+
+    for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
+         it != filter_list_->end(); ++it) {
+      DBusSigFilter *filter = (*it);
+      if (!filter) {
+        LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
+        continue;
+      }
+
+      LATE(dbus_bus_add_match)(
+          LATE(dbus_g_connection_get_connection)(connection_),
+          filter->filter().c_str(), NULL);
+
+      if (!LATE(dbus_connection_add_filter)(
+              LATE(dbus_g_connection_get_connection)(connection_),
+              &DBusSigFilter::DBusCallback, filter, NULL)) {
+        LOG(LS_ERROR) << "dbus_connection_add_filter() failed."
+                      << "Filter: " << filter->filter();
+        continue;
+      }
+    }
+  }
+
+  // Unregisters all DBus filters.
+  void UnRegisterAllFilters() {
+    ASSERT(NULL != LATE(dbus_g_connection_get_connection)(connection_));
+
+    for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
+         it != filter_list_->end(); ++it) {
+      DBusSigFilter *filter = (*it);
+      if (!filter) {
+        LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
+        continue;
+      }
+      LATE(dbus_connection_remove_filter)(
+          LATE(dbus_g_connection_get_connection)(connection_),
+          &DBusSigFilter::DBusCallback, filter);
+    }
+  }
+
+  // Sets up the monitoring thread.
+  bool Setup() {
+    LATE(g_main_context_push_thread_default)(context_);
+
+    // Start connection to dbus.
+    // If dbus daemon is not running, returns false immediately.
+    connection_ = LATE(dbus_g_bus_get_private)(monitor_->type_, context_, NULL);
+    if (NULL == connection_) {
+      LOG(LS_ERROR) << "dbus_g_bus_get_private() unable to get connection.";
+      return false;
+    }
+    if (NULL == LATE(dbus_g_connection_get_connection)(connection_)) {
+      LOG(LS_ERROR) << "dbus_g_connection_get_connection() returns NULL. "
+                    << "DBus daemon is probably not running.";
+      return false;
+    }
+
+    // Application don't exit if DBus daemon die.
+    LATE(dbus_connection_set_exit_on_disconnect)(
+        LATE(dbus_g_connection_get_connection)(connection_), FALSE);
+
+    // Connect all filters.
+    RegisterAllFilters();
+
+    return true;
+  }
+
+  // Cleans up the monitoring thread.
+  void CleanUp() {
+    if (idle_source_) {
+      // We did an attach() with the GSource, so we need to destroy() it.
+      LATE(g_source_destroy)(idle_source_);
+      // We need to unref() the GSource to end the last reference we got.
+      LATE(g_source_unref)(idle_source_);
+      idle_source_ = NULL;
+    }
+    if (connection_) {
+      if (LATE(dbus_g_connection_get_connection)(connection_)) {
+        UnRegisterAllFilters();
+        LATE(dbus_connection_close)(
+            LATE(dbus_g_connection_get_connection)(connection_));
+      }
+      LATE(dbus_g_connection_unref)(connection_);
+      connection_ = NULL;
+    }
+    LATE(g_main_loop_unref)(mainloop_);
+    mainloop_ = NULL;
+    LATE(g_main_context_unref)(context_);
+    context_ = NULL;
+  }
+
+  // Handles callback on Idle. We only add this source when ready to stop.
+  static gboolean Idle(gpointer data) {
+    static_cast<DBusMonitoringThread *>(data)->QuitGMainloop();
+    return TRUE;
+  }
+
+  // We only hit this when ready to quit.
+  void QuitGMainloop() {
+    LATE(g_main_loop_quit)(mainloop_);
+  }
+
+  DBusMonitor *monitor_;
+
+  GMainContext *context_;
+  GMainLoop *mainloop_;
+  DBusGConnection *connection_;
+  GSource *idle_source_;
+
+  std::vector<DBusSigFilter *> *filter_list_;
+};
+
+// Implementation of class DBusMonitor
+
+// Returns DBus-Glib symbol handle. Initialize it first if hasn't.
+LibDBusGlibSymbolTable *DBusMonitor::GetDBusGlibSymbolTable() {
+  // This is multi-thread safe.
+  pthread_once(&g_dbus_init_once, InitializeDBusGlibSymbol);
+
+  return g_dbus_symbol;
+};
+
+// Creates an instance of DBusMonitor
+DBusMonitor *DBusMonitor::Create(DBusBusType type) {
+  if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) {
+    return NULL;
+  }
+  return new DBusMonitor(type);
+}
+
+DBusMonitor::DBusMonitor(DBusBusType type)
+    : type_(type),
+      status_(DMS_NOT_INITIALIZED),
+      monitoring_thread_(NULL) {
+  ASSERT(type_ == DBUS_BUS_SYSTEM || type_ == DBUS_BUS_SESSION);
+}
+
+DBusMonitor::~DBusMonitor() {
+  StopMonitoring();
+}
+
+bool DBusMonitor::AddFilter(DBusSigFilter *filter) {
+  if (monitoring_thread_) {
+    return false;
+  }
+  if (!filter) {
+    return false;
+  }
+  filter_list_.push_back(filter);
+  return true;
+}
+
+bool DBusMonitor::StartMonitoring() {
+  if (!monitoring_thread_) {
+    LATE(g_type_init)();
+    LATE(g_thread_init)(NULL);
+    LATE(dbus_g_thread_init)();
+
+    GMainContext *context = LATE(g_main_context_new)();
+    if (NULL == context) {
+      LOG(LS_ERROR) << "g_main_context_new() failed.";
+      return false;
+    }
+
+    GMainLoop *mainloop = LATE(g_main_loop_new)(context, FALSE);
+    if (NULL == mainloop) {
+      LOG(LS_ERROR) << "g_main_loop_new() failed.";
+      LATE(g_main_context_unref)(context);
+      return false;
+    }
+
+    monitoring_thread_ = new DBusMonitoringThread(this, context, mainloop,
+                                                  &filter_list_);
+    if (monitoring_thread_ == NULL) {
+      LOG(LS_ERROR) << "Failed to create DBus monitoring thread.";
+      LATE(g_main_context_unref)(context);
+      LATE(g_main_loop_unref)(mainloop);
+      return false;
+    }
+    monitoring_thread_->Start();
+  }
+  return true;
+}
+
+bool DBusMonitor::StopMonitoring() {
+  if (monitoring_thread_) {
+    monitoring_thread_->Stop();
+    monitoring_thread_ = NULL;
+  }
+  return true;
+}
+
+DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() {
+  return status_;
+}
+
+void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) {
+  status_ = status;
+}
+
+#undef LATE
+
+}  // namespace talk_base
+
+#endif  // HAVE_DBUS_GLIB
diff --git a/talk/base/dbus.h b/talk/base/dbus.h
new file mode 100644
index 0000000..7dce350
--- /dev/null
+++ b/talk/base/dbus.h
@@ -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.
+ */
+
+#ifndef TALK_BASE_DBUS_H_
+#define TALK_BASE_DBUS_H_
+
+#ifdef HAVE_DBUS_GLIB
+
+#include <dbus/dbus.h>
+
+#include <string>
+#include <vector>
+
+#include "talk/base/libdbusglibsymboltable.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+#define DBUS_TYPE                   "type"
+#define DBUS_SIGNAL                 "signal"
+#define DBUS_PATH                   "path"
+#define DBUS_INTERFACE              "interface"
+#define DBUS_MEMBER                 "member"
+
+#ifdef CHROMEOS
+#define CROS_PM_PATH                "/"
+#define CROS_PM_INTERFACE           "org.chromium.PowerManager"
+#define CROS_SIG_POWERCHANGED       "PowerStateChanged"
+#define CROS_VALUE_SLEEP            "mem"
+#define CROS_VALUE_RESUME           "on"
+#else
+#define UP_PATH                     "/org/freedesktop/UPower"
+#define UP_INTERFACE                "org.freedesktop.UPower"
+#define UP_SIG_SLEEPING             "Sleeping"
+#define UP_SIG_RESUMING             "Resuming"
+#endif  // CHROMEOS
+
+// Wraps a DBus messages.
+class DBusSigMessageData : public TypedMessageData<DBusMessage *> {
+ public:
+  explicit DBusSigMessageData(DBusMessage *message);
+  ~DBusSigMessageData();
+};
+
+// DBusSigFilter is an abstract class that defines the interface of DBus
+// signal handling.
+// The subclasses implement ProcessSignal() for various purposes.
+// When a DBus signal comes, a DSM_SIGNAL message is posted to the caller thread
+// which will then invokes ProcessSignal().
+class DBusSigFilter : protected MessageHandler {
+ public:
+  enum DBusSigMessage { DSM_SIGNAL };
+
+  // This filter string should ususally come from BuildFilterString()
+  explicit DBusSigFilter(const std::string &filter)
+      : caller_thread_(Thread::Current()), filter_(filter) {
+  }
+
+  // Builds a DBus monitor filter string from given DBus path, interface, and
+  // member.
+  // See http://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
+  static std::string BuildFilterString(const std::string &path,
+                                       const std::string &interface,
+                                       const std::string &member);
+
+  // Handles callback on DBus messages by DBus system.
+  static DBusHandlerResult DBusCallback(DBusConnection *dbus_conn,
+                                        DBusMessage *message,
+                                        void *instance);
+
+  // Handles callback on DBus messages to each DBusSigFilter instance.
+  DBusHandlerResult Callback(DBusMessage *message);
+
+  // From MessageHandler.
+  virtual void OnMessage(Message *message);
+
+  // Returns the DBus monitor filter string.
+  const std::string &filter() const { return filter_; }
+
+ private:
+  // On caller thread.
+  virtual void ProcessSignal(DBusMessage *message) = 0;
+
+  Thread *caller_thread_;
+  const std::string filter_;
+};
+
+// DBusMonitor is a class for DBus signal monitoring.
+//
+// The caller-thread calls AddFilter() first to add the signals that it wants to
+// monitor and then calls StartMonitoring() to start the monitoring.
+// This will create a worker-thread which listens on DBus connection and sends
+// DBus signals back through the callback.
+// The worker-thread will be running forever until either StopMonitoring() is
+// called from the caller-thread or the worker-thread hit some error.
+//
+// Programming model:
+//   1. Caller-thread: Creates an object of DBusMonitor.
+//   2. Caller-thread: Calls DBusMonitor::AddFilter() one or several times.
+//   3. Caller-thread: StartMonitoring().
+//      ...
+//   4. Worker-thread: DBus signal recieved. Post a message to caller-thread.
+//   5. Caller-thread: DBusFilterBase::ProcessSignal() is invoked.
+//      ...
+//   6. Caller-thread: StopMonitoring().
+//
+// Assumption:
+//   AddFilter(), StartMonitoring(), and StopMonitoring() methods are called by
+//   a single thread. Hence, there is no need to make them thread safe.
+class DBusMonitor {
+ public:
+  // Status of DBus monitoring.
+  enum DBusMonitorStatus {
+    DMS_NOT_INITIALIZED,  // Not initialized.
+    DMS_INITIALIZING,     // Initializing the monitoring thread.
+    DMS_RUNNING,          // Monitoring.
+    DMS_STOPPED,          // Not monitoring. Stopped normally.
+    DMS_FAILED,           // Not monitoring. Failed.
+  };
+
+  // Returns the DBus-Glib symbol table.
+  // We should only use this function to access DBus-Glib symbols.
+  static LibDBusGlibSymbolTable *GetDBusGlibSymbolTable();
+
+  // Creates an instance of DBusMonitor.
+  static DBusMonitor *Create(DBusBusType type);
+  ~DBusMonitor();
+
+  // Adds a filter to DBusMonitor.
+  bool AddFilter(DBusSigFilter *filter);
+
+  // Starts DBus message monitoring.
+  bool StartMonitoring();
+
+  // Stops DBus message monitoring.
+  bool StopMonitoring();
+
+  // Gets the status of DBus monitoring.
+  DBusMonitorStatus GetStatus();
+
+ private:
+  // Forward declaration. Defined in the .cc file.
+  class DBusMonitoringThread;
+
+  explicit DBusMonitor(DBusBusType type);
+
+  // Updates status_ when monitoring status has changed.
+  void OnMonitoringStatusChanged(DBusMonitorStatus status);
+
+  DBusBusType type_;
+  DBusMonitorStatus status_;
+  DBusMonitoringThread *monitoring_thread_;
+  std::vector<DBusSigFilter *> filter_list_;
+};
+
+}  // namespace talk_base
+
+#endif  // HAVE_DBUS_GLIB
+
+#endif  // TALK_BASE_DBUS_H_
diff --git a/talk/base/dbus_unittest.cc b/talk/base/dbus_unittest.cc
new file mode 100644
index 0000000..2b9a545
--- /dev/null
+++ b/talk/base/dbus_unittest.cc
@@ -0,0 +1,249 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_DBUS_GLIB
+
+#include "talk/base/dbus.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+#define SIG_NAME "NameAcquired"
+
+static const uint32 kTimeoutMs = 5000U;
+
+class DBusSigFilterTest : public DBusSigFilter {
+ public:
+  // DBusSigFilterTest listens on DBus service itself for "NameAcquired" signal.
+  // This signal should be received when the application connects to DBus
+  // service and gains ownership of a name.
+  // http://dbus.freedesktop.org/doc/dbus-specification.html
+  DBusSigFilterTest()
+      : DBusSigFilter(GetFilter()),
+        message_received_(false) {
+  }
+
+  bool MessageReceived() {
+    return message_received_;
+  }
+
+ private:
+  static std::string GetFilter() {
+    return talk_base::DBusSigFilter::BuildFilterString("", "", SIG_NAME);
+  }
+
+  // Implement virtual method of DBusSigFilter. On caller thread.
+  virtual void ProcessSignal(DBusMessage *message) {
+    EXPECT_TRUE(message != NULL);
+    message_received_ = true;
+  }
+
+  bool message_received_;
+};
+
+TEST(DBusMonitorTest, StartStopStartStop) {
+  DBusSigFilterTest filter;
+  talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+  monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+  if (monitor.get()) {
+    EXPECT_TRUE(monitor->AddFilter(&filter));
+
+    EXPECT_TRUE(monitor->StopMonitoring());
+    EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_NOT_INITIALIZED);
+
+    EXPECT_TRUE(monitor->StartMonitoring());
+    EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+    EXPECT_TRUE(monitor->StopMonitoring());
+    EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+    EXPECT_TRUE(monitor->StopMonitoring());
+    EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+
+    EXPECT_TRUE(monitor->StartMonitoring());
+    EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+    EXPECT_TRUE(monitor->StartMonitoring());
+    EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_RUNNING);
+    EXPECT_TRUE(monitor->StopMonitoring());
+    EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+  } else {
+    LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+  }
+}
+
+// DBusMonitorTest listens on DBus service itself for "NameAcquired" signal.
+// This signal should be received when the application connects to DBus
+// service and gains ownership of a name.
+// This test is to make sure that we capture the "NameAcquired" signal.
+TEST(DBusMonitorTest, ReceivedNameAcquiredSignal) {
+  DBusSigFilterTest filter;
+  talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+  monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+  if (monitor.get()) {
+    EXPECT_TRUE(monitor->AddFilter(&filter));
+
+    EXPECT_TRUE(monitor->StartMonitoring());
+    EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+    EXPECT_TRUE_WAIT(filter.MessageReceived(), kTimeoutMs);
+    EXPECT_TRUE(monitor->StopMonitoring());
+    EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+  } else {
+    LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+  }
+}
+
+TEST(DBusMonitorTest, ConcurrentMonitors) {
+  DBusSigFilterTest filter1;
+  talk_base::scoped_ptr<talk_base::DBusMonitor> monitor1;
+  monitor1.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+  if (monitor1.get()) {
+    EXPECT_TRUE(monitor1->AddFilter(&filter1));
+    DBusSigFilterTest filter2;
+    talk_base::scoped_ptr<talk_base::DBusMonitor> monitor2;
+    monitor2.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+    EXPECT_TRUE(monitor2->AddFilter(&filter2));
+
+    EXPECT_TRUE(monitor1->StartMonitoring());
+    EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor1->GetStatus(), kTimeoutMs);
+    EXPECT_TRUE(monitor2->StartMonitoring());
+    EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor2->GetStatus(), kTimeoutMs);
+
+    EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs);
+    EXPECT_TRUE(monitor2->StopMonitoring());
+    EXPECT_EQ(monitor2->GetStatus(), DBusMonitor::DMS_STOPPED);
+
+    EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs);
+    EXPECT_TRUE(monitor1->StopMonitoring());
+    EXPECT_EQ(monitor1->GetStatus(), DBusMonitor::DMS_STOPPED);
+  } else {
+    LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+  }
+}
+
+TEST(DBusMonitorTest, ConcurrentFilters) {
+  DBusSigFilterTest filter1;
+  DBusSigFilterTest filter2;
+  talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+  monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+  if (monitor.get()) {
+    EXPECT_TRUE(monitor->AddFilter(&filter1));
+    EXPECT_TRUE(monitor->AddFilter(&filter2));
+
+    EXPECT_TRUE(monitor->StartMonitoring());
+    EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+
+    EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs);
+    EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs);
+
+    EXPECT_TRUE(monitor->StopMonitoring());
+    EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+  } else {
+    LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+  }
+}
+
+TEST(DBusMonitorTest, NoAddFilterIfRunning) {
+  DBusSigFilterTest filter1;
+  DBusSigFilterTest filter2;
+  talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+  monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+  if (monitor.get()) {
+    EXPECT_TRUE(monitor->AddFilter(&filter1));
+
+    EXPECT_TRUE(monitor->StartMonitoring());
+    EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+    EXPECT_FALSE(monitor->AddFilter(&filter2));
+
+    EXPECT_TRUE(monitor->StopMonitoring());
+    EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+  } else {
+    LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+  }
+}
+
+TEST(DBusMonitorTest, AddFilterAfterStop) {
+  DBusSigFilterTest filter1;
+  DBusSigFilterTest filter2;
+  talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+  monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+  if (monitor.get()) {
+    EXPECT_TRUE(monitor->AddFilter(&filter1));
+    EXPECT_TRUE(monitor->StartMonitoring());
+    EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+    EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs);
+    EXPECT_TRUE(monitor->StopMonitoring());
+    EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+
+    EXPECT_TRUE(monitor->AddFilter(&filter2));
+    EXPECT_TRUE(monitor->StartMonitoring());
+    EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+    EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs);
+    EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs);
+    EXPECT_TRUE(monitor->StopMonitoring());
+    EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+  } else {
+    LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+  }
+}
+
+TEST(DBusMonitorTest, StopRightAfterStart) {
+  DBusSigFilterTest filter;
+  talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+  monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+  if (monitor.get()) {
+    EXPECT_TRUE(monitor->AddFilter(&filter));
+
+    EXPECT_TRUE(monitor->StartMonitoring());
+    EXPECT_TRUE(monitor->StopMonitoring());
+
+    // Stop the monitoring thread right after it had been started.
+    // If the monitoring thread got a chance to receive a DBus signal, it would
+    // post a message to the main thread and signal the main thread wakeup.
+    // This message will be cleaned out automatically when the filter get
+    // destructed. Here we also consume the wakeup signal (if there is one) so
+    // that the testing (main) thread is reset to a clean state.
+    talk_base::Thread::Current()->ProcessMessages(1);
+  } else {
+    LOG(LS_WARNING) << "DBus Monitor not started.";
+  }
+}
+
+TEST(DBusSigFilter, BuildFilterString) {
+  EXPECT_EQ(DBusSigFilter::BuildFilterString("", "", ""),
+      (DBUS_TYPE "='" DBUS_SIGNAL "'"));
+  EXPECT_EQ(DBusSigFilter::BuildFilterString("p", "", ""),
+      (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p'"));
+  EXPECT_EQ(DBusSigFilter::BuildFilterString("p","i", ""),
+      (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p',"
+          DBUS_INTERFACE "='i'"));
+  EXPECT_EQ(DBusSigFilter::BuildFilterString("p","i","m"),
+      (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p',"
+          DBUS_INTERFACE "='i'," DBUS_MEMBER "='m'"));
+}
+
+}  // namespace talk_base
+
+#endif  // HAVE_DBUS_GLIB
diff --git a/talk/base/diskcache_win32.cc b/talk/base/diskcache_win32.cc
new file mode 100644
index 0000000..b49ed81
--- /dev/null
+++ b/talk/base/diskcache_win32.cc
@@ -0,0 +1,103 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/win32.h"
+#include <shellapi.h>
+#include <shlobj.h>
+#include <tchar.h>
+
+#include <time.h>
+
+#include "talk/base/common.h"
+#include "talk/base/diskcache.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+#include "talk/base/diskcache_win32.h"
+
+namespace talk_base {
+
+bool DiskCacheWin32::InitializeEntries() {
+  // Note: We could store the cache information in a separate file, for faster
+  // initialization.  Figuring it out empirically works, too.
+
+  std::wstring path16 = ToUtf16(folder_);
+  path16.append(1, '*');
+
+  WIN32_FIND_DATA find_data;
+  HANDLE find_handle = FindFirstFile(path16.c_str(), &find_data);
+  if (find_handle != INVALID_HANDLE_VALUE) {
+    do {
+      size_t index;
+      std::string id;
+      if (!FilenameToId(ToUtf8(find_data.cFileName), &id, &index))
+        continue;
+
+      Entry* entry = GetOrCreateEntry(id, true);
+      entry->size += find_data.nFileSizeLow;
+      total_size_ += find_data.nFileSizeLow;
+      entry->streams = _max(entry->streams, index + 1);
+      FileTimeToUnixTime(find_data.ftLastWriteTime, &entry->last_modified);
+
+    } while (FindNextFile(find_handle, &find_data));
+
+    FindClose(find_handle);
+  }
+
+  return true;
+}
+
+bool DiskCacheWin32::PurgeFiles() {
+  std::wstring path16 = ToUtf16(folder_);
+  path16.append(1, '*');
+  path16.append(1, '\0');
+
+  SHFILEOPSTRUCT file_op = { 0 };
+  file_op.wFunc = FO_DELETE;
+  file_op.pFrom = path16.c_str();
+  file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT
+                   | FOF_NORECURSION | FOF_FILESONLY;
+  if (0 != SHFileOperation(&file_op)) {
+    LOG_F(LS_ERROR) << "Couldn't delete cache files";
+    return false;
+  }
+
+  return true;
+}
+
+bool DiskCacheWin32::FileExists(const std::string& filename) const {
+  DWORD result = ::GetFileAttributes(ToUtf16(filename).c_str());
+  return (INVALID_FILE_ATTRIBUTES != result);
+}
+
+bool DiskCacheWin32::DeleteFile(const std::string& filename) const {
+  return ::DeleteFile(ToUtf16(filename).c_str()) != 0;
+}
+
+}
diff --git a/talk/base/diskcache_win32.h b/talk/base/diskcache_win32.h
new file mode 100644
index 0000000..a5e8de5
--- /dev/null
+++ b/talk/base/diskcache_win32.h
@@ -0,0 +1,46 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_DISKCACHEWIN32_H__
+#define TALK_BASE_DISKCACHEWIN32_H__
+
+#include "talk/base/diskcache.h"
+
+namespace talk_base {
+
+class DiskCacheWin32 : public DiskCache {
+ protected:
+  virtual bool InitializeEntries();
+  virtual bool PurgeFiles();
+
+  virtual bool FileExists(const std::string& filename) const;
+  virtual bool DeleteFile(const std::string& filename) const;
+};
+
+}
+
+#endif  // TALK_BASE_DISKCACHEWIN32_H__
diff --git a/talk/base/fakenetwork.h b/talk/base/fakenetwork.h
new file mode 100644
index 0000000..c5ae4b6
--- /dev/null
+++ b/talk/base/fakenetwork.h
@@ -0,0 +1,106 @@
+/*
+ * libjingle
+ * Copyright 2009 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_FAKENETWORK_H_
+#define TALK_BASE_FAKENETWORK_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/network.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// Fake network manager that allows us to manually specify the IPs to use.
+class FakeNetworkManager : public NetworkManagerBase,
+                           public MessageHandler {
+ public:
+  FakeNetworkManager()
+      : thread_(Thread::Current()),
+        next_index_(0),
+        started_(false) {
+  }
+
+  void AddInterface(const SocketAddress& iface) {
+    // ensure a unique name for the interface
+    SocketAddress address("test" + talk_base::ToString(next_index_++), 0);
+    address.SetResolvedIP(iface.ipaddr());
+    ifaces_.push_back(address);
+    DoUpdateNetworks();
+  }
+
+  void RemoveInterface(const SocketAddress& iface) {
+    for (std::vector<SocketAddress>::iterator it = ifaces_.begin();
+         it != ifaces_.end(); ++it) {
+      if (it->EqualIPs(iface)) {
+        ifaces_.erase(it);
+        break;
+      }
+    }
+    DoUpdateNetworks();
+  }
+
+  virtual void StartUpdating() {
+    started_ = true;
+    thread_->Post(this);
+  }
+
+  virtual void StopUpdating() {
+    started_ = false;
+  }
+
+  // MessageHandler interface.
+  virtual void OnMessage(Message* msg) {
+    DoUpdateNetworks();
+  }
+
+ private:
+  void DoUpdateNetworks() {
+    if (!started_)
+      return;
+    std::vector<Network*> networks;
+    for (std::vector<SocketAddress>::iterator it = ifaces_.begin();
+         it != ifaces_.end(); ++it) {
+      networks.push_back(new Network(it->hostname(), it->hostname(),
+                                     it->ipaddr()));
+    }
+    MergeNetworkList(networks, true);
+  }
+
+  Thread* thread_;
+  std::vector<SocketAddress> ifaces_;
+  int next_index_;
+  bool started_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FAKENETWORK_H_
diff --git a/talk/base/faketaskrunner.h b/talk/base/faketaskrunner.h
new file mode 100644
index 0000000..6b5b035
--- /dev/null
+++ b/talk/base/faketaskrunner.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+// A fake TaskRunner for use in unit tests.
+
+#ifndef TALK_BASE_FAKETASKRUNNER_H_
+#define TALK_BASE_FAKETASKRUNNER_H_
+
+#include "talk/base/taskparent.h"
+#include "talk/base/taskrunner.h"
+
+namespace talk_base {
+
+class FakeTaskRunner : public TaskRunner {
+ public:
+  FakeTaskRunner() : current_time_(0) {}
+  virtual ~FakeTaskRunner() {}
+
+  virtual void WakeTasks() { RunTasks(); }
+
+  virtual int64 CurrentTime() {
+    // Implement if needed.
+    return current_time_++;
+  }
+
+  int64 current_time_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FAKETASKRUNNER_H_
diff --git a/talk/base/filelock.cc b/talk/base/filelock.cc
new file mode 100644
index 0000000..2bc7045
--- /dev/null
+++ b/talk/base/filelock.cc
@@ -0,0 +1,79 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/filelock.h"
+
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+FileLock::FileLock(const std::string& path, FileStream* file)
+    : path_(path), file_(file) {
+}
+
+FileLock::~FileLock() {
+  MaybeUnlock();
+}
+
+void FileLock::Unlock() {
+  LOG_F(LS_INFO);
+  MaybeUnlock();
+}
+
+void FileLock::MaybeUnlock() {
+  if (file_.get() != NULL) {
+    LOG(LS_INFO) << "Unlocking:" << path_;
+    file_->Close();
+    Filesystem::DeleteFile(path_);
+    file_.reset();
+  }
+}
+
+FileLock* FileLock::TryLock(const std::string& path) {
+  FileStream* stream = new FileStream();
+  bool ok = false;
+#ifdef WIN32
+  // Open and lock in a single operation.
+  ok = stream->OpenShare(path, "a", _SH_DENYRW, NULL);
+#else // Linux and OSX
+  ok = stream->Open(path, "a", NULL) && stream->TryLock();
+#endif
+  if (ok) {
+    return new FileLock(path, stream);
+  } else {
+    // Something failed, either we didn't succeed to open the
+    // file or we failed to lock it. Anyway remove the heap
+    // allocated object and then return NULL to indicate failure.
+    delete stream;
+    return NULL;
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/filelock.h b/talk/base/filelock.h
new file mode 100644
index 0000000..a4936f5
--- /dev/null
+++ b/talk/base/filelock.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_FILELOCK_H_
+#define TALK_BASE_FILELOCK_H_
+
+#include <string>
+
+#include "talk/base/constructormagic.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class FileStream;
+
+// Implements a very simple cross process lock based on a file.
+// When Lock(...) is called we try to open/create the file in read/write
+// mode without any sharing. (Or locking it with flock(...) on Unix)
+// If the process crash the OS will make sure that the file descriptor
+// is released and another process can accuire the lock.
+// This doesn't work on ancient OSX/Linux versions if used on NFS.
+// (Nfs-client before: ~2.6 and Linux Kernel < 2.6.)
+class FileLock {
+ public:
+  virtual ~FileLock();
+
+  // Attempts to lock the file. The caller owns the returned
+  // lock object. Returns NULL if the file already was locked.
+  static FileLock* TryLock(const std::string& path);
+  void Unlock();
+
+ protected:
+  FileLock(const std::string& path, FileStream* file);
+
+ private:
+  void MaybeUnlock();
+
+  std::string path_;
+  scoped_ptr<FileStream> file_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(FileLock);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FILELOCK_H_
diff --git a/talk/base/filelock_unittest.cc b/talk/base/filelock_unittest.cc
new file mode 100644
index 0000000..e585f91
--- /dev/null
+++ b/talk/base/filelock_unittest.cc
@@ -0,0 +1,104 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/event.h"
+#include "talk/base/filelock.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+const static std::string kLockFile = "TestLockFile";
+const static int kTimeoutMS = 5000;
+
+class FileLockTest : public testing::Test, public Runnable {
+ public:
+  FileLockTest() : done_(false, false), thread_lock_failed_(false) {
+  }
+
+  virtual void Run(Thread* t) {
+    scoped_ptr<FileLock> lock(FileLock::TryLock(temp_file_.pathname()));
+    // The lock is already owned by the main thread of
+    // this test, therefore the TryLock(...) call should fail.
+    thread_lock_failed_ = lock.get() == NULL;
+    done_.Set();
+  }
+
+ protected:
+  virtual void SetUp() {
+    thread_lock_failed_ = false;
+    Filesystem::GetAppTempFolder(&temp_dir_);
+    temp_file_ = Pathname(temp_dir_.pathname(), kLockFile);
+  }
+
+  void LockOnThread() {
+    locker_.Start(this);
+    done_.Wait(kTimeoutMS);
+  }
+
+  Event done_;
+  Thread locker_;
+  bool thread_lock_failed_;
+  Pathname temp_dir_;
+  Pathname temp_file_;
+};
+
+TEST_F(FileLockTest, TestLockFileDeleted) {
+  scoped_ptr<FileLock> lock(FileLock::TryLock(temp_file_.pathname()));
+  EXPECT_TRUE(lock.get() != NULL);
+  EXPECT_FALSE(Filesystem::IsAbsent(temp_file_.pathname()));
+  lock->Unlock();
+  EXPECT_TRUE(Filesystem::IsAbsent(temp_file_.pathname()));
+}
+
+TEST_F(FileLockTest, TestLock) {
+  scoped_ptr<FileLock> lock(FileLock::TryLock(temp_file_.pathname()));
+  EXPECT_TRUE(lock.get() != NULL);
+}
+
+TEST_F(FileLockTest, TestLockX2) {
+  scoped_ptr<FileLock> lock1(FileLock::TryLock(temp_file_.pathname()));
+  EXPECT_TRUE(lock1.get() != NULL);
+
+  scoped_ptr<FileLock> lock2(FileLock::TryLock(temp_file_.pathname()));
+  EXPECT_TRUE(lock2.get() == NULL);
+}
+
+TEST_F(FileLockTest, TestThreadedLock) {
+  scoped_ptr<FileLock> lock(FileLock::TryLock(temp_file_.pathname()));
+  EXPECT_TRUE(lock.get() != NULL);
+
+  LockOnThread();
+  EXPECT_TRUE(thread_lock_failed_);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/fileutils.cc b/talk/base/fileutils.cc
index 4504d29..7b0d348 100644
--- a/talk/base/fileutils.cc
+++ b/talk/base/fileutils.cc
@@ -154,15 +154,17 @@
 #endif
 }
 
-scoped_ptr<FilesystemInterface> Filesystem::default_filesystem_;
+FilesystemInterface* Filesystem::default_filesystem_ = NULL;
+
 FilesystemInterface *Filesystem::EnsureDefaultFilesystem() {
-  if (!default_filesystem_.get())
+  if (!default_filesystem_) {
 #ifdef WIN32
-    default_filesystem_.reset(new Win32Filesystem());
+    default_filesystem_ = new Win32Filesystem();
 #else
-    default_filesystem_.reset(new UnixFilesystem());
+    default_filesystem_ = new UnixFilesystem();
 #endif
-    return default_filesystem_.get();
+  }
+  return default_filesystem_;
 }
 
 bool FilesystemInterface::CopyFolder(const Pathname &old_path,
diff --git a/talk/base/fileutils.h b/talk/base/fileutils.h
index 644516d..186c963 100644
--- a/talk/base/fileutils.h
+++ b/talk/base/fileutils.h
@@ -285,18 +285,18 @@
 class Filesystem {
  public:
   static FilesystemInterface *default_filesystem() {
-    ASSERT(default_filesystem_.get() != NULL);
-    return default_filesystem_.get();
+    ASSERT(default_filesystem_ != NULL);
+    return default_filesystem_;
   }
 
   static void set_default_filesystem(FilesystemInterface *filesystem) {
-    default_filesystem_.reset(filesystem);
+    default_filesystem_ = filesystem;
   }
 
   static FilesystemInterface *swap_default_filesystem(
       FilesystemInterface *filesystem) {
-    FilesystemInterface *cur = default_filesystem_.release();
-    default_filesystem_.reset(filesystem);
+    FilesystemInterface *cur = default_filesystem_;
+    default_filesystem_ = filesystem;
     return cur;
   }
 
@@ -425,7 +425,7 @@
   }
 
  private:
-  static scoped_ptr<FilesystemInterface> default_filesystem_;
+  static FilesystemInterface* default_filesystem_;
 
   static FilesystemInterface *EnsureDefaultFilesystem();
   DISALLOW_IMPLICIT_CONSTRUCTORS(Filesystem);
@@ -455,4 +455,3 @@
 }  // namespace talk_base
 
 #endif  // TALK_BASE_FILEUTILS_H_
-
diff --git a/talk/base/fileutils_mock.h b/talk/base/fileutils_mock.h
index 2ccb6a3..b91e802 100644
--- a/talk/base/fileutils_mock.h
+++ b/talk/base/fileutils_mock.h
@@ -33,9 +33,9 @@
 #include <vector>
 
 #include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
 #include "talk/base/pathutils.h"
 #include "talk/base/stream.h"
-#include "testing/base/public/gmock.h"
 
 namespace talk_base {
 
@@ -168,81 +168,81 @@
     }
 
     bool CreatePrivateFile(const Pathname &filename) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool DeleteFile(const Pathname &filename) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool DeleteEmptyFolder(const Pathname &folder) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool DeleteFolderContents(const Pathname &folder) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool DeleteFolderAndContents(const Pathname &folder) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool CreateFolder(const Pathname &pathname) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool MoveFolder(const Pathname &old_path, const Pathname &new_path) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool MoveFile(const Pathname &old_path, const Pathname &new_path) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool CopyFile(const Pathname &old_path, const Pathname &new_path) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool IsFolder(const Pathname &pathname) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool IsFile(const Pathname &pathname) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool IsAbsent(const Pathname &pathname) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool IsTemporaryPath(const Pathname &pathname) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool GetTemporaryFolder(Pathname &path, bool create,
                             const std::string *append) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     std::string TempFilename(const Pathname &dir, const std::string &prefix) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return std::string();
     }
     bool GetFileSize(const Pathname &path, size_t *size) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool GetFileTime(const Pathname &path, FileTimeType which,
                      time_t* time) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool GetAppPathname(Pathname *path) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool GetAppDataFolder(Pathname *path, bool per_user) {
-      EXPECT_TRUE_M(per_user, "Unsupported operation");
+      EXPECT_TRUE(per_user) << "Unsupported operation";
 #ifdef WIN32
       path->SetPathname("c:\\Users\\test_user", "");
 #else
@@ -251,11 +251,11 @@
       return true;
     }
     bool GetAppTempFolder(Pathname *path) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     bool GetDiskFreeSpace(const Pathname &path, int64 *freebytes) {
-      EXPECT_TRUE_M(false, "Unsupported operation");
+      EXPECT_TRUE(false) << "Unsupported operation";
       return false;
     }
     Pathname GetCurrentDirectory() {
diff --git a/talk/base/firewallsocketserver.cc b/talk/base/firewallsocketserver.cc
index a99c72e..71385f8 100644
--- a/talk/base/firewallsocketserver.cc
+++ b/talk/base/firewallsocketserver.cc
@@ -177,11 +177,11 @@
     const Rule& r = rules_[i];
     if ((r.p != p) && (r.p != FP_ANY))
       continue;
-    if ((r.src.ip() != src.ip()) && !r.src.IsAny())
+    if ((r.src.ipaddr() != src.ipaddr()) && !r.src.IsAny())
       continue;
     if ((r.src.port() != src.port()) && (r.src.port() != 0))
       continue;
-    if ((r.dst.ip() != dst.ip()) && !r.dst.IsAny())
+    if ((r.dst.ipaddr() != dst.ipaddr()) && !r.dst.IsAny())
       continue;
     if ((r.dst.port() != dst.port()) && (r.dst.port() != 0))
       continue;
diff --git a/talk/base/flags.h b/talk/base/flags.h
index a6eee00..f22e125 100644
--- a/talk/base/flags.h
+++ b/talk/base/flags.h
@@ -25,6 +25,9 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+
+// Originally comes from shared/commandlineflags/flags.h
+
 // Flags are defined and declared using DEFINE_xxx and DECLARE_xxx macros,
 // where xxx is the flag type. Flags are referred to via FLAG_yyy,
 // where yyy is the flag name. For intialization and iteration of flags,
diff --git a/talk/base/gunit.h b/talk/base/gunit.h
index 9d04595..0682337 100644
--- a/talk/base/gunit.h
+++ b/talk/base/gunit.h
@@ -109,21 +109,6 @@
     } \
   } while (0);
 
-#ifdef __LP64__
-// When compiling in 64-bit mode we redefine the TEST and TEST_F macros to
-// append _64bit to all testsuite names to distinguish the test results from
-// those for the 32-bit cross-built binaries.
-#undef TEST
-#undef TEST_F
-// Copied and adjusted from google3/third_party/gtest/include/gtest/gtest.h
-#define TEST(test_case_name, test_name)\
-  GTEST_TEST_(test_case_name##_64bit, test_name, \
-              ::testing::Test, ::testing::internal::GetTestTypeId())
-#define TEST_F(test_fixture, test_name)\
-  GTEST_TEST_(test_fixture##_64bit, test_name, test_fixture, \
-              ::testing::internal::GetTypeId<test_fixture>())
-#endif
-
 talk_base::Pathname GetTalkDirectory();
 
 #endif  // TALK_BASE_GUNIT_H_
diff --git a/talk/base/helpers.cc b/talk/base/helpers.cc
index 5b0e447..366a0fd 100644
--- a/talk/base/helpers.cc
+++ b/talk/base/helpers.cc
@@ -40,9 +40,10 @@
 #endif
 
 #include "talk/base/base64.h"
+#include "talk/base/basictypes.h"
 #include "talk/base/logging.h"
 #include "talk/base/scoped_ptr.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 
 // Protect against max macro inclusion.
 #undef max
@@ -206,8 +207,9 @@
 // This round about way of creating a global RNG is to safe-guard against
 // indeterminant static initialization order.
 scoped_ptr<RandomGenerator>& GetGlobalRng() {
-  static scoped_ptr<RandomGenerator> g_rng(new SecureRandomGenerator());
-  return g_rng;
+  LIBJINGLE_DEFINE_STATIC_LOCAL(scoped_ptr<RandomGenerator>, global_rng,
+                                (new SecureRandomGenerator()));
+  return global_rng;
 }
 
 RandomGenerator& Rng() {
diff --git a/talk/base/ipaddress.cc b/talk/base/ipaddress.cc
new file mode 100644
index 0000000..036584f
--- /dev/null
+++ b/talk/base/ipaddress.cc
@@ -0,0 +1,388 @@
+/*
+ * 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/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#ifdef OPENBSD
+#include <netinet/in_systm.h>
+#endif
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+
+#include "talk/base/ipaddress.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/win32.h"
+
+namespace talk_base {
+
+static const unsigned char kMappedPrefix[] = {0x00, 0x00, 0x00, 0x00,
+                                              0x00, 0x00, 0x00, 0x00,
+                                              0x00, 0x00, 0xFF, 0xFF};
+static bool IsPrivateV4(uint32 ip);
+static bool IsMappedAddress(const in6_addr& addr);
+static in_addr ExtractMappedAddress(const in6_addr& addr);
+
+uint32 IPAddress::v4AddressAsHostOrderInteger() const {
+  if (family_ == AF_INET) {
+    return ntohl(u_.ip4.s_addr);
+  } else {
+    return 0;
+  }
+}
+
+size_t IPAddress::Size() const {
+  switch (family_) {
+    case AF_INET:
+      return sizeof(in_addr);
+    case AF_INET6:
+      return sizeof(in6_addr);
+  }
+  return 0;
+}
+
+
+bool IPAddress::operator==(const IPAddress &other) const {
+  if (family_ != other.family_) {
+    return false;
+  }
+  if (family_ == AF_INET) {
+    return memcmp(&u_.ip4, &other.u_.ip4, sizeof(u_.ip4)) == 0;
+  }
+  if (family_ == AF_INET6) {
+    return memcmp(&u_.ip6, &other.u_.ip6, sizeof(u_.ip6)) == 0;
+  }
+  return family_ == AF_UNSPEC;
+}
+
+bool IPAddress::operator!=(const IPAddress &other) const {
+  return !((*this) == other);
+}
+
+bool IPAddress::operator >(const IPAddress &other) const {
+  return (*this) != other && !((*this) < other);
+}
+
+bool IPAddress::operator <(const IPAddress &other) const {
+  // IPv4 is 'less than' IPv6
+  if (family_ != other.family_) {
+    if (family_ == AF_UNSPEC) {
+      return true;
+    }
+    if (family_ == AF_INET && other.family_ == AF_INET6) {
+      return true;
+    }
+    return false;
+  }
+  // Comparing addresses of the same family.
+  switch (family_) {
+    case AF_INET: {
+      return ntohl(u_.ip4.s_addr) < ntohl(other.u_.ip4.s_addr);
+    }
+    case AF_INET6: {
+      return memcmp(&u_.ip6.s6_addr, &other.u_.ip6.s6_addr, 16) < 0;
+    }
+  }
+  // Catches AF_UNSPEC and invalid addresses.
+  return false;
+}
+
+std::ostream& operator<<(std::ostream& os, const IPAddress& ip) {
+  os << ip.ToString();
+  return os;
+}
+
+in6_addr IPAddress::ipv6_address() const {
+  return u_.ip6;
+}
+
+in_addr IPAddress::ipv4_address() const {
+  return u_.ip4;
+}
+
+std::string IPAddress::ToString() const {
+  if (family_ != AF_INET && family_ != AF_INET6) {
+    return std::string();
+  }
+  char buf[INET6_ADDRSTRLEN] = {0};
+  const void* src = &u_.ip4;
+  if (family_ == AF_INET6) {
+    src = &u_.ip6;
+  }
+  if (!talk_base::inet_ntop(family_, src, buf, sizeof(buf))) {
+    return std::string();
+  }
+  return std::string(buf);
+}
+
+IPAddress IPAddress::Normalized() const {
+  if (family_ != AF_INET6) {
+    return *this;
+  }
+  if (!IsMappedAddress(u_.ip6)) {
+    return *this;
+  }
+  in_addr addr = ExtractMappedAddress(u_.ip6);
+  return IPAddress(addr);
+}
+
+IPAddress IPAddress::AsIPv6Address() const {
+  if (family_ != AF_INET) {
+    return *this;
+  }
+  //  uint32 v4 = (u_.ip4.s_addr);
+  in6_addr v6addr;
+  ::memcpy(&v6addr.s6_addr, kMappedPrefix, sizeof(kMappedPrefix));
+  ::memcpy(&v6addr.s6_addr[12], &u_.ip4.s_addr, sizeof(u_.ip4.s_addr));
+  return IPAddress(v6addr);
+}
+
+bool IsPrivateV4(uint32 ip_in_host_order) {
+  return ((ip_in_host_order >> 24) == 127) ||
+      ((ip_in_host_order >> 24) == 10) ||
+      ((ip_in_host_order >> 20) == ((172 << 4) | 1)) ||
+      ((ip_in_host_order >> 16) == ((192 << 8) | 168)) ||
+      ((ip_in_host_order >> 16) == ((169 << 8) | 254));
+}
+
+bool IsMappedAddress(const in6_addr& addr) {
+  return memcmp(&(addr.s6_addr), kMappedPrefix, sizeof(kMappedPrefix)) == 0;
+}
+
+in_addr ExtractMappedAddress(const in6_addr& in6) {
+  in_addr ipv4;
+  ::memcpy(&ipv4.s_addr, &in6.s6_addr[12], sizeof(ipv4.s_addr));
+  return ipv4;
+}
+
+bool IPFromHostEnt(hostent* host_ent, IPAddress* out) {
+  return IPFromHostEnt(host_ent, 0, out);
+}
+
+bool IPFromHostEnt(hostent* host_ent, int idx, IPAddress* out) {
+  if (!out || (idx < 0)) {
+    return false;
+  }
+  char** requested_address = host_ent->h_addr_list;
+  // Find the idx-th element (while checking for null, which terminates the
+  // list of addresses).
+  while (*requested_address && idx) {
+    idx--;
+    requested_address++;
+  }
+  if (!(*requested_address)) {
+    return false;
+  }
+
+  if (host_ent->h_addrtype == AF_INET) {
+    in_addr ip;
+    ip.s_addr = *reinterpret_cast<uint32*>(*requested_address);
+    *out = IPAddress(ip);
+    return true;
+  } else if (host_ent->h_addrtype == AF_INET6) {
+    in6_addr ip;
+    ::memcpy(&ip.s6_addr, *requested_address, host_ent->h_length);
+    *out = IPAddress(ip);
+    return true;
+  }
+  return false;
+}
+
+bool IPFromString(const std::string& str, IPAddress* out) {
+  if (!out) {
+    return false;
+  }
+  in_addr addr;
+  if (talk_base::inet_pton(AF_INET, str.c_str(), &addr) == 0) {
+    in6_addr addr6;
+    if (talk_base::inet_pton(AF_INET6, str.c_str(), &addr6) == 0) {
+      *out = IPAddress();
+      return false;
+    }
+    *out = IPAddress(addr6);
+  } else {
+    *out = IPAddress(addr);
+  }
+  return true;
+}
+
+bool IPIsAny(const IPAddress& ip) {
+  static const IPAddress kIPv4Any(INADDR_ANY);
+  static const IPAddress kIPv6Any(in6addr_any);
+  switch (ip.family()) {
+    case AF_INET:
+      return ip == kIPv4Any;
+    case AF_INET6:
+      return ip == kIPv6Any;
+    case AF_UNSPEC:
+      return false;
+  }
+  return false;
+}
+
+bool IPIsLoopback(const IPAddress& ip) {
+  static const IPAddress kIPv4Loopback(INADDR_LOOPBACK);
+  static const IPAddress kIPv6Loopback(in6addr_loopback);
+  switch (ip.family()) {
+    case AF_INET: {
+      return ip == kIPv4Loopback;
+    }
+    case AF_INET6: {
+      return ip == kIPv6Loopback;
+    }
+  }
+  return false;
+}
+
+bool IPIsPrivate(const IPAddress& ip) {
+  switch (ip.family()) {
+    case AF_INET: {
+      return IsPrivateV4(ip.v4AddressAsHostOrderInteger());
+    }
+    case AF_INET6: {
+      in6_addr v6 = ip.ipv6_address();
+      return (v6.s6_addr[0] == 0xFE && v6.s6_addr[1] == 0x80) ||
+          IPIsLoopback(ip);
+    }
+  }
+  return false;
+}
+
+size_t HashIP(const IPAddress& ip) {
+  switch (ip.family()) {
+    case AF_INET: {
+      return ip.ipv4_address().s_addr;
+    }
+    case AF_INET6: {
+      in6_addr v6addr = ip.ipv6_address();
+      const uint32* v6_as_ints =
+          reinterpret_cast<const uint32*>(&v6addr.s6_addr);
+      return v6_as_ints[0] ^ v6_as_ints[1] ^ v6_as_ints[2] ^ v6_as_ints[3];
+    }
+  }
+  return 0;
+}
+
+IPAddress TruncateIP(const IPAddress& ip, int length) {
+  if (length < 0) {
+    return IPAddress();
+  }
+  if (ip.family() == AF_INET) {
+    if (length > 31) {
+      return ip;
+    }
+    if (length == 0) {
+      return IPAddress(INADDR_ANY);
+    }
+    int mask = (0xFFFFFFFF << (32 - length));
+    uint32 host_order_ip = NetworkToHost32(ip.ipv4_address().s_addr);
+    in_addr masked;
+    masked.s_addr = HostToNetwork32(host_order_ip & mask);
+    return IPAddress(masked);
+  } else if (ip.family() == AF_INET6) {
+    if (length > 127) {
+      return ip;
+    }
+    if (length == 0) {
+      return IPAddress(in6addr_any);
+    }
+    in6_addr v6addr = ip.ipv6_address();
+    int position = length / 32;
+    int inner_length = (length - (position * 32));
+    int inner_mask = (0xFFFFFFFF  << (32 - inner_length));
+    uint32* v6_as_ints =
+        reinterpret_cast<uint32*>(&v6addr.s6_addr);
+    in6_addr ip_addr = ip.ipv6_address();
+    for (int i = 0; i < 4; ++i) {
+      if (i == position) {
+        uint32 host_order_inner = NetworkToHost32(v6_as_ints[i]);
+        v6_as_ints[i] = HostToNetwork32(host_order_inner & inner_mask);
+      } else if (i > position) {
+        v6_as_ints[i] = 0;
+      }
+    }
+    return IPAddress(v6addr);
+  }
+  return IPAddress();
+}
+
+int CountIPMaskBits(IPAddress mask) {
+  // Doing this the lazy/simple way.
+  // Clever bit tricks welcome but please be careful re: byte order.
+  uint32 word_to_count = 0;
+  int bits = 0;
+  switch (mask.family()) {
+    case AF_INET: {
+      word_to_count = NetworkToHost32(mask.ipv4_address().s_addr);
+      break;
+    }
+    case AF_INET6: {
+      in6_addr v6addr = mask.ipv6_address();
+      const uint32* v6_as_ints =
+          reinterpret_cast<const uint32*>(&v6addr.s6_addr);
+      int i = 0;
+      for (; i < 4; ++i) {
+        if (v6_as_ints[i] != 0xFFFFFFFF) {
+          break;
+        }
+      }
+      if (i < 4) {
+        word_to_count = NetworkToHost32(v6_as_ints[i]);
+      }
+      bits = (i * 32);
+      break;
+    }
+    default: {
+      return 0;
+    }
+  }
+  // Check for byte boundaries before scanning.
+  if (word_to_count == 0) {
+    return bits;
+  } else if (word_to_count == 0xFF000000) {
+    return bits + 8;
+  } else if (word_to_count == 0xFFFF0000) {
+    return bits + 16;
+  } else if (word_to_count == 0xFFFFFF00) {
+    return bits + 24;
+  }
+
+  while (word_to_count & 0x80000000) {
+    word_to_count <<= 1;
+    ++bits;
+  }
+  return bits;
+}
+}  // Namespace talk base
diff --git a/talk/base/ipaddress.h b/talk/base/ipaddress.h
new file mode 100644
index 0000000..b1063fd
--- /dev/null
+++ b/talk/base/ipaddress.h
@@ -0,0 +1,136 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_IPADDRESS_H_
+#define TALK_BASE_IPADDRESS_H_
+
+#ifdef POSIX
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#endif
+#ifdef WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+#include <string.h>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+namespace talk_base {
+
+// Version-agnostic IP address class, wraps a union of in_addr and in6_addr.
+class IPAddress {
+ public:
+  IPAddress() : family_(AF_UNSPEC) {
+    ::memset(&u_, 0, sizeof(u_));
+  }
+
+  explicit IPAddress(const in_addr &ip4) : family_(AF_INET) {
+    memset(&u_, 0, sizeof(u_));
+    u_.ip4 = ip4;
+  }
+
+  explicit IPAddress(const in6_addr &ip6) : family_(AF_INET6) {
+    u_.ip6 = ip6;
+  }
+
+  explicit IPAddress(uint32 ip_in_host_byte_order) : family_(AF_INET) {
+    memset(&u_, 0, sizeof(u_));
+    u_.ip4.s_addr = htonl(ip_in_host_byte_order);
+  }
+
+  IPAddress(const IPAddress &other) : family_(other.family_) {
+    ::memcpy(&u_, &other.u_, sizeof(u_));
+  }
+
+  ~IPAddress() {}
+
+  const IPAddress & operator=(const IPAddress &other) {
+    family_ = other.family_;
+    ::memcpy(&u_, &other.u_, sizeof(u_));
+    return *this;
+  }
+
+  bool operator==(const IPAddress &other) const;
+  bool operator!=(const IPAddress &other) const;
+  bool operator <(const IPAddress &other) const;
+  bool operator >(const IPAddress &other) const;
+  friend std::ostream& operator<<(std::ostream& os, const IPAddress& addr);
+
+  int family() const { return family_; }
+  in_addr ipv4_address() const;
+  in6_addr ipv6_address() const;
+
+  // Returns the number of bytes needed to store the raw address.
+  size_t Size() const;
+
+  // Wraps inet_ntop.
+  std::string ToString() const;
+
+  // Returns an unmapped address from a possibly-mapped address.
+  // Returns the same address if this isn't a mapped address.
+  IPAddress Normalized() const;
+
+  // Returns this address as an IPv6 address.
+  // Maps v4 addresses (as ::ffff:a.b.c.d), returns v6 addresses unchanged.
+  IPAddress AsIPv6Address() const;
+
+  // For socketaddress' benefit. Returns the IP in host byte order.
+  uint32 v4AddressAsHostOrderInteger() const;
+
+ private:
+  int family_;
+  union {
+    in_addr ip4;
+    in6_addr ip6;
+  } u_;
+};
+
+bool IPFromHostEnt(hostent* hostEnt, IPAddress* out);
+bool IPFromHostEnt(hostent* hostEnt, int idx, IPAddress* out);
+bool IPFromString(const std::string& str, IPAddress* out);
+bool IPIsAny(const IPAddress& ip);
+bool IPIsLoopback(const IPAddress& ip);
+bool IPIsPrivate(const IPAddress& ip);
+size_t HashIP(const IPAddress& ip);
+
+// Returns 'ip' truncated to be 'length' bits long.
+IPAddress TruncateIP(const IPAddress& ip, int length);
+// Returns the number of contiguously set bits, counting from the MSB in network
+// byte order, in this IPAddress. Bits after the first 0 encountered are not
+// counted.
+int CountIPMaskBits(IPAddress mask);
+}  // namespace talk_base
+
+#endif  // TALK_BASE_IPADDRESS_H_
diff --git a/talk/base/ipaddress_unittest.cc b/talk/base/ipaddress_unittest.cc
new file mode 100644
index 0000000..40cf187
--- /dev/null
+++ b/talk/base/ipaddress_unittest.cc
@@ -0,0 +1,765 @@
+/*
+ * 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/ipaddress.h"
+
+namespace talk_base {
+
+static const unsigned int kIPv4AddrSize = 4;
+static const unsigned int kIPv6AddrSize = 16;
+static const unsigned int kIPv4RFC1918Addr = 0xC0A80701;
+static const unsigned int kIPv4PublicAddr = 0x01020304;
+static const in6_addr kIPv6LinkLocalAddr = {{{0xfe, 0x80, 0x00, 0x00,
+                                              0x00, 0x00, 0x00, 0x00,
+                                              0xbe, 0x30, 0x5b, 0xff,
+                                              0xfe, 0xe5, 0x00, 0xc3}}};
+static const in6_addr kIPv6PublicAddr = {{{0x24, 0x01, 0xfa, 0x00,
+                                           0x00, 0x04, 0x10, 0x00,
+                                           0xbe, 0x30, 0x5b, 0xff,
+                                           0xfe, 0xe5, 0x00, 0xc3}}};
+static const in6_addr kIPv6CompatAddr = {{{0x00, 0x00, 0x00, 0x00,
+                                           0x00, 0x00, 0x00, 0x00,
+                                           0x00, 0x00, 0x00, 0x00,
+                                           0xfe, 0xe5, 0x00, 0xc3}}};
+static const in6_addr kIPv4MappedAnyAddr = {{{0x00, 0x00, 0x00, 0x00,
+                                              0x00, 0x00, 0x00, 0x00,
+                                              0x00, 0x00, 0xff, 0xff,
+                                              0x00, 0x00, 0x00, 0x00}}};
+static const in6_addr kIPv4MappedLoopbackAddr = {{{0x00, 0x00, 0x00, 0x00,
+                                                   0x00, 0x00, 0x00, 0x00,
+                                                   0x00, 0x00, 0xff, 0xff,
+                                                   0x7f, 0x00, 0x00, 0x01}}};
+static const in6_addr kIPv4MappedRFC1918Addr = {{{0x00, 0x00, 0x00, 0x00,
+                                                  0x00, 0x00, 0x00, 0x00,
+                                                  0x00, 0x00, 0xff, 0xff,
+                                                  0xc0, 0xa8, 0x07, 0x01}}};
+static const in6_addr kIPv4MappedPublicAddr = {{{0x00, 0x00, 0x00, 0x00,
+                                                 0x00, 0x00, 0x00, 0x00,
+                                                 0x00, 0x00, 0xff, 0xff,
+                                                 0x01, 0x02, 0x03, 0x04}}};
+static const in6_addr kIPv6AllNodes = {{{0xff, 0x02, 0x00, 0x00,
+                                         0x00, 0x00, 0x00, 0x00,
+                                         0x00, 0x00, 0x00, 0x00,
+                                         0x00, 0x00, 0x00, 0x01}}};
+
+static const std::string kIPv4AnyAddrString = "0.0.0.0";
+static const std::string kIPv4LoopbackAddrString = "127.0.0.1";
+static const std::string kIPv4RFC1918AddrString = "192.168.7.1";
+static const std::string kIPv4PublicAddrString = "1.2.3.4";
+static const std::string kIPv6AnyAddrString = "::";
+static const std::string kIPv6LoopbackAddrString = "::1";
+static const std::string kIPv6LinkLocalAddrString = "fe80::be30:5bff:fee5:c3";
+static const std::string kIPv6PublicAddrString =
+    "2401:fa00:4:1000:be30:5bff:fee5:c3";
+static const std::string kIPv4MappedAnyAddrString = "::ffff:0:0";
+static const std::string kIPv4MappedRFC1918AddrString = "::ffff:c0a8:701";
+static const std::string kIPv4MappedLoopbackAddrString = "::ffff:7f00:1";
+static const std::string kIPv4MappedPublicAddrString = "::ffff:102:0304";
+static const std::string kIPv4MappedV4StyleAddrString = "::ffff:192.168.7.1";
+
+static const std::string kIPv4BrokenString1 = "192.168.7.";
+static const std::string kIPv4BrokenString2 = "192.168.7.1.1";
+static const std::string kIPv4BrokenString3 = "192.168.7.1:80";
+static const std::string kIPv4BrokenString4 = "192.168.7.ONE";
+static const std::string kIPv4BrokenString5 = "-192.168.7.1";
+static const std::string kIPv4BrokenString6 = "256.168.7.1";
+static const std::string kIPv6BrokenString1 = "2401:fa00:4:1000:be30";
+static const std::string kIPv6BrokenString2 =
+    "2401:fa00:4:1000:be30:5bff:fee5:c3:1";
+static const std::string kIPv6BrokenString3 =
+    "[2401:fa00:4:1000:be30:5bff:fee5:c3]:1";
+static const std::string kIPv6BrokenString4 =
+    "2401::4::be30";
+static const std::string kIPv6BrokenString5 =
+    "2401:::4:fee5:be30";
+static const std::string kIPv6BrokenString6 =
+    "2401f:fa00:4:1000:be30:5bff:fee5:c3";
+static const std::string kIPv6BrokenString7 =
+    "2401:ga00:4:1000:be30:5bff:fee5:c3";
+static const std::string kIPv6BrokenString8 =
+    "2401:fa000:4:1000:be30:5bff:fee5:c3";
+static const std::string kIPv6BrokenString9 =
+    "2401:fal0:4:1000:be30:5bff:fee5:c3";
+static const std::string kIPv6BrokenString10 =
+    "::ffff:192.168.7.";
+static const std::string kIPv6BrokenString11 =
+    "::ffff:192.168.7.1.1.1";
+static const std::string kIPv6BrokenString12 =
+    "::fffe:192.168.7.1";
+static const std::string kIPv6BrokenString13 =
+    "::ffff:192.168.7.ff";
+static const std::string kIPv6BrokenString14 =
+    "0x2401:fa00:4:1000:be30:5bff:fee5:c3";
+
+bool IPFromHostEntWorks(const std::string& name, int expected_family,
+                        IPAddress expected_addr) {
+  struct hostent* ent = gethostbyname(name.c_str());
+  if (ent) {
+    IPAddress addr;
+    if (!IPFromHostEnt(ent, &addr)) {
+      return false;
+    }
+    return addr == expected_addr;
+  }
+  return true;
+}
+
+bool AreEqual(const IPAddress& addr,
+              const IPAddress& addr2) {
+  if ((IPIsAny(addr) != IPIsAny(addr2)) ||
+      (IPIsLoopback(addr) != IPIsLoopback(addr2)) ||
+      (IPIsPrivate(addr) != IPIsPrivate(addr2)) ||
+      (HashIP(addr) != HashIP(addr2)) ||
+      (addr.Size() != addr2.Size()) ||
+      (addr.family() != addr2.family()) ||
+      (addr.ToString() != addr2.ToString())) {
+    return false;
+  }
+  in_addr v4addr, v4addr2;
+  v4addr = addr.ipv4_address();
+  v4addr2 = addr2.ipv4_address();
+  if (0 != memcmp(&v4addr, &v4addr2, sizeof(v4addr))) {
+    return false;
+  }
+  in6_addr v6addr, v6addr2;
+  v6addr = addr.ipv6_address();
+  v6addr2 = addr2.ipv6_address();
+  if (0 != memcmp(&v6addr, &v6addr2, sizeof(v6addr))) {
+    return false;
+  }
+  return true;
+}
+
+bool BrokenIPStringFails(const std::string& broken) {
+  IPAddress addr(0);   // Intentionally make it v4.
+  if (IPFromString(kIPv4BrokenString1, &addr)) {
+    return false;
+  }
+  return addr.family() == AF_UNSPEC;
+}
+
+bool CheckMaskCount(const std::string& mask, int expected_length) {
+  IPAddress addr;
+  return IPFromString(mask, &addr) &&
+      (expected_length == CountIPMaskBits(addr));
+}
+
+bool CheckTruncateIP(const std::string& initial, int truncate_length,
+                     const std::string& expected_result) {
+  IPAddress addr, expected;
+  IPFromString(initial, &addr);
+  IPFromString(expected_result, &expected);
+  IPAddress truncated = TruncateIP(addr, truncate_length);
+  return truncated == expected;
+}
+
+TEST(IPAddressTest, TestDefaultCtor) {
+  IPAddress addr;
+  EXPECT_FALSE(IPIsAny(addr));
+  EXPECT_FALSE(IPIsLoopback(addr));
+  EXPECT_FALSE(IPIsPrivate(addr));
+
+  EXPECT_EQ(0U, addr.Size());
+  EXPECT_EQ(AF_UNSPEC, addr.family());
+  EXPECT_EQ("", addr.ToString());
+}
+
+TEST(IPAddressTest, TestInAddrCtor) {
+  in_addr v4addr;
+
+  // Test V4 Any address.
+  v4addr.s_addr = INADDR_ANY;
+  IPAddress addr(v4addr);
+  EXPECT_TRUE(IPIsAny(addr));
+  EXPECT_FALSE(IPIsLoopback(addr));
+  EXPECT_FALSE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv4AddrSize, addr.Size());
+  EXPECT_EQ(kIPv4AnyAddrString, addr.ToString());
+
+  // Test a V4 loopback address.
+  v4addr.s_addr = htonl(INADDR_LOOPBACK);
+  addr = IPAddress(v4addr);
+  EXPECT_FALSE(IPIsAny(addr));
+  EXPECT_TRUE(IPIsLoopback(addr));
+  EXPECT_TRUE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv4AddrSize, addr.Size());
+  EXPECT_EQ(kIPv4LoopbackAddrString, addr.ToString());
+
+  // Test an RFC1918 address.
+  v4addr.s_addr = htonl(kIPv4RFC1918Addr);
+  addr = IPAddress(v4addr);
+  EXPECT_FALSE(IPIsAny(addr));
+  EXPECT_FALSE(IPIsLoopback(addr));
+  EXPECT_TRUE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv4AddrSize, addr.Size());
+  EXPECT_EQ(kIPv4RFC1918AddrString, addr.ToString());
+
+  // Test a 'normal' v4 address.
+  v4addr.s_addr = htonl(kIPv4PublicAddr);
+  addr = IPAddress(v4addr);
+  EXPECT_FALSE(IPIsAny(addr));
+  EXPECT_FALSE(IPIsLoopback(addr));
+  EXPECT_FALSE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv4AddrSize, addr.Size());
+  EXPECT_EQ(kIPv4PublicAddrString, addr.ToString());
+}
+
+TEST(IPAddressTest, TestInAddr6Ctor) {
+  // Test v6 empty.
+  IPAddress addr(in6addr_any);
+  EXPECT_TRUE(IPIsAny(addr));
+  EXPECT_FALSE(IPIsLoopback(addr));
+  EXPECT_FALSE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv6AddrSize, addr.Size());
+  EXPECT_EQ(kIPv6AnyAddrString, addr.ToString());
+
+  // Test v6 loopback.
+  addr = IPAddress(in6addr_loopback);
+  EXPECT_FALSE(IPIsAny(addr));
+  EXPECT_TRUE(IPIsLoopback(addr));
+  EXPECT_TRUE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv6AddrSize, addr.Size());
+  EXPECT_EQ(kIPv6LoopbackAddrString, addr.ToString());
+
+  // Test v6 link-local.
+  addr = IPAddress(kIPv6LinkLocalAddr);
+  EXPECT_FALSE(IPIsAny(addr));
+  EXPECT_FALSE(IPIsLoopback(addr));
+  EXPECT_TRUE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv6AddrSize, addr.Size());
+  EXPECT_EQ(kIPv6LinkLocalAddrString, addr.ToString());
+
+  // Test v6 global address.
+  addr = IPAddress(kIPv6PublicAddr);
+  EXPECT_FALSE(IPIsAny(addr));
+  EXPECT_FALSE(IPIsLoopback(addr));
+  EXPECT_FALSE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv6AddrSize, addr.Size());
+  EXPECT_EQ(kIPv6PublicAddrString, addr.ToString());
+}
+
+TEST(IPAddressTest, TestUint32Ctor) {
+  // Test V4 Any address.
+  IPAddress addr(0);
+  EXPECT_TRUE(IPIsAny(addr));
+  EXPECT_FALSE(IPIsLoopback(addr));
+  EXPECT_FALSE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv4AddrSize, addr.Size());
+  EXPECT_EQ(kIPv4AnyAddrString, addr.ToString());
+
+  // Test a V4 loopback address.
+  addr = IPAddress(INADDR_LOOPBACK);
+  EXPECT_FALSE(IPIsAny(addr));
+  EXPECT_TRUE(IPIsLoopback(addr));
+  EXPECT_TRUE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv4AddrSize, addr.Size());
+  EXPECT_EQ(kIPv4LoopbackAddrString, addr.ToString());
+
+  // Test an RFC1918 address.
+  addr = IPAddress(kIPv4RFC1918Addr);
+  EXPECT_FALSE(IPIsAny(addr));
+  EXPECT_FALSE(IPIsLoopback(addr));
+  EXPECT_TRUE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv4AddrSize, addr.Size());
+  EXPECT_EQ(kIPv4RFC1918AddrString, addr.ToString());
+
+  // Test a 'normal' v4 address.
+  addr = IPAddress(kIPv4PublicAddr);
+  EXPECT_FALSE(IPIsAny(addr));
+  EXPECT_FALSE(IPIsLoopback(addr));
+  EXPECT_FALSE(IPIsPrivate(addr));
+  EXPECT_EQ(kIPv4AddrSize, addr.Size());
+  EXPECT_EQ(kIPv4PublicAddrString, addr.ToString());
+}
+
+TEST(IPAddressTest, TestHostEntCtor) {
+  IPAddress addr(INADDR_LOOPBACK);
+  EXPECT_PRED3(IPFromHostEntWorks, "localhost", AF_INET, addr);
+
+  addr = IPAddress(kIPv6AllNodes);
+  EXPECT_PRED3(IPFromHostEntWorks, "ip6-allnodes", AF_INET6, addr);
+
+  //  gethostbyname works for literal addresses too
+  addr = IPAddress(INADDR_ANY);
+  EXPECT_PRED3(IPFromHostEntWorks,
+               kIPv4AnyAddrString, AF_INET, addr);
+  addr = IPAddress(kIPv4RFC1918Addr);
+  EXPECT_PRED3(IPFromHostEntWorks,
+               kIPv4RFC1918AddrString, AF_INET, addr);
+  addr = IPAddress(kIPv4PublicAddr);
+  EXPECT_PRED3(IPFromHostEntWorks,
+               kIPv4PublicAddrString, AF_INET, addr);
+
+  addr = IPAddress(in6addr_any);
+  EXPECT_PRED3(IPFromHostEntWorks,
+               kIPv6AnyAddrString, AF_INET6, addr);
+  addr = IPAddress(in6addr_loopback);
+  EXPECT_PRED3(IPFromHostEntWorks,
+               kIPv6LoopbackAddrString, AF_INET6, addr);
+  addr = IPAddress(kIPv6LinkLocalAddr);
+  EXPECT_PRED3(IPFromHostEntWorks,
+               kIPv6LinkLocalAddrString, AF_INET6, addr);
+  addr = IPAddress(kIPv6PublicAddr);
+  EXPECT_PRED3(IPFromHostEntWorks,
+               kIPv6PublicAddrString, AF_INET6, addr);
+}
+
+TEST(IPAddressTest, TestCopyCtor) {
+  in_addr v4addr;
+  v4addr.s_addr = htonl(kIPv4PublicAddr);
+  IPAddress addr(v4addr);
+  IPAddress addr2(addr);
+
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr = IPAddress(INADDR_ANY);
+  addr2 = IPAddress(addr);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr = IPAddress(INADDR_LOOPBACK);
+  addr2 = IPAddress(addr);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr = IPAddress(kIPv4PublicAddr);
+  addr2 = IPAddress(addr);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr = IPAddress(kIPv4RFC1918Addr);
+  addr2 = IPAddress(addr);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr = IPAddress(in6addr_any);
+  addr2 = IPAddress(addr);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr = IPAddress(in6addr_loopback);
+  addr2 = IPAddress(addr);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr = IPAddress(kIPv6LinkLocalAddr);
+  addr2 = IPAddress(addr);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr = IPAddress(kIPv6PublicAddr);
+  addr2 = IPAddress(addr);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+}
+
+TEST(IPAddressTest, TestEquality) {
+  // Check v4 equality
+  in_addr v4addr, v4addr2;
+  v4addr.s_addr = htonl(kIPv4PublicAddr);
+  v4addr2.s_addr = htonl(kIPv4PublicAddr + 1);
+  IPAddress addr(v4addr);
+  IPAddress addr2(v4addr2);
+  IPAddress addr3(v4addr);
+
+  EXPECT_TRUE(addr == addr);
+  EXPECT_TRUE(addr2 == addr2);
+  EXPECT_TRUE(addr3 == addr3);
+  EXPECT_TRUE(addr == addr3);
+  EXPECT_TRUE(addr3 == addr);
+  EXPECT_FALSE(addr2 == addr);
+  EXPECT_FALSE(addr2 == addr3);
+  EXPECT_FALSE(addr == addr2);
+  EXPECT_FALSE(addr3 == addr2);
+
+  // Check v6 equality
+  IPAddress addr4(kIPv6PublicAddr);
+  IPAddress addr5(kIPv6LinkLocalAddr);
+  IPAddress addr6(kIPv6PublicAddr);
+
+  EXPECT_TRUE(addr4 == addr4);
+  EXPECT_TRUE(addr5 == addr5);
+  EXPECT_TRUE(addr4 == addr6);
+  EXPECT_TRUE(addr6 == addr4);
+  EXPECT_FALSE(addr4 == addr5);
+  EXPECT_FALSE(addr5 == addr4);
+  EXPECT_FALSE(addr6 == addr5);
+  EXPECT_FALSE(addr5 == addr6);
+
+  // Check v4/v6 cross-equality
+  EXPECT_FALSE(addr == addr4);
+  EXPECT_FALSE(addr == addr5);
+  EXPECT_FALSE(addr == addr6);
+  EXPECT_FALSE(addr4 == addr);
+  EXPECT_FALSE(addr5 == addr);
+  EXPECT_FALSE(addr6 == addr);
+  EXPECT_FALSE(addr2 == addr4);
+  EXPECT_FALSE(addr2 == addr5);
+  EXPECT_FALSE(addr2 == addr6);
+  EXPECT_FALSE(addr4 == addr2);
+  EXPECT_FALSE(addr5 == addr2);
+  EXPECT_FALSE(addr6 == addr2);
+  EXPECT_FALSE(addr3 == addr4);
+  EXPECT_FALSE(addr3 == addr5);
+  EXPECT_FALSE(addr3 == addr6);
+  EXPECT_FALSE(addr4 == addr3);
+  EXPECT_FALSE(addr5 == addr3);
+  EXPECT_FALSE(addr6 == addr3);
+
+  // Special cases: loopback and any.
+  // They're special but they're still not equal.
+  IPAddress v4loopback(htonl(INADDR_LOOPBACK));
+  IPAddress v6loopback(in6addr_loopback);
+  EXPECT_FALSE(v4loopback == v6loopback);
+
+  IPAddress v4any(0);
+  IPAddress v6any(in6addr_any);
+  EXPECT_FALSE(v4any == v6any);
+}
+
+TEST(IPAddressTest, TestComparison) {
+  // Defined in 'ascending' order.
+  // v6 > v4, and intra-family sorting is purely numerical
+  IPAddress addr0;  // AF_UNSPEC
+  IPAddress addr1(INADDR_ANY);  // 0.0.0.0
+  IPAddress addr2(kIPv4PublicAddr);  // 1.2.3.4
+  IPAddress addr3(INADDR_LOOPBACK);  // 127.0.0.1
+  IPAddress addr4(kIPv4RFC1918Addr);  // 192.168.7.1.
+  IPAddress addr5(in6addr_any);  // ::
+  IPAddress addr6(in6addr_loopback);  // ::1
+  IPAddress addr7(kIPv6PublicAddr);  // 2401....
+  IPAddress addr8(kIPv6LinkLocalAddr);  // fe80....
+
+  EXPECT_TRUE(addr0 < addr1);
+  EXPECT_TRUE(addr1 < addr2);
+  EXPECT_TRUE(addr2 < addr3);
+  EXPECT_TRUE(addr3 < addr4);
+  EXPECT_TRUE(addr4 < addr5);
+  EXPECT_TRUE(addr5 < addr6);
+  EXPECT_TRUE(addr6 < addr7);
+  EXPECT_TRUE(addr7 < addr8);
+
+  EXPECT_FALSE(addr0 > addr1);
+  EXPECT_FALSE(addr1 > addr2);
+  EXPECT_FALSE(addr2 > addr3);
+  EXPECT_FALSE(addr3 > addr4);
+  EXPECT_FALSE(addr4 > addr5);
+  EXPECT_FALSE(addr5 > addr6);
+  EXPECT_FALSE(addr6 > addr7);
+  EXPECT_FALSE(addr7 > addr8);
+
+  EXPECT_FALSE(addr0 > addr0);
+  EXPECT_FALSE(addr1 > addr1);
+  EXPECT_FALSE(addr2 > addr2);
+  EXPECT_FALSE(addr3 > addr3);
+  EXPECT_FALSE(addr4 > addr4);
+  EXPECT_FALSE(addr5 > addr5);
+  EXPECT_FALSE(addr6 > addr6);
+  EXPECT_FALSE(addr7 > addr7);
+  EXPECT_FALSE(addr8 > addr8);
+
+  EXPECT_FALSE(addr0 < addr0);
+  EXPECT_FALSE(addr1 < addr1);
+  EXPECT_FALSE(addr2 < addr2);
+  EXPECT_FALSE(addr3 < addr3);
+  EXPECT_FALSE(addr4 < addr4);
+  EXPECT_FALSE(addr5 < addr5);
+  EXPECT_FALSE(addr6 < addr6);
+  EXPECT_FALSE(addr7 < addr7);
+  EXPECT_FALSE(addr8 < addr8);
+}
+
+TEST(IPAddressTest, TestFromString) {
+  IPAddress addr;
+  IPAddress addr2;
+  addr2 = IPAddress(INADDR_ANY);
+
+  EXPECT_TRUE(IPFromString(kIPv4AnyAddrString, &addr));
+  EXPECT_EQ(addr.ToString(), kIPv4AnyAddrString);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr2 = IPAddress(INADDR_LOOPBACK);
+  EXPECT_TRUE(IPFromString(kIPv4LoopbackAddrString, &addr));
+  EXPECT_EQ(addr.ToString(), kIPv4LoopbackAddrString);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr2 = IPAddress(kIPv4RFC1918Addr);
+  EXPECT_TRUE(IPFromString(kIPv4RFC1918AddrString, &addr));
+  EXPECT_EQ(addr.ToString(), kIPv4RFC1918AddrString);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr2 = IPAddress(kIPv4PublicAddr);
+  EXPECT_TRUE(IPFromString(kIPv4PublicAddrString, &addr));
+  EXPECT_EQ(addr.ToString(), kIPv4PublicAddrString);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr2 = IPAddress(in6addr_any);
+  EXPECT_TRUE(IPFromString(kIPv6AnyAddrString, &addr));
+  EXPECT_EQ(addr.ToString(), kIPv6AnyAddrString);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr2 = IPAddress(in6addr_loopback);
+  EXPECT_TRUE(IPFromString(kIPv6LoopbackAddrString, &addr));
+  EXPECT_EQ(addr.ToString(), kIPv6LoopbackAddrString);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr2 = IPAddress(kIPv6LinkLocalAddr);
+  EXPECT_TRUE(IPFromString(kIPv6LinkLocalAddrString, &addr));
+  EXPECT_EQ(addr.ToString(), kIPv6LinkLocalAddrString);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr2 = IPAddress(kIPv6PublicAddr);
+  EXPECT_TRUE(IPFromString(kIPv6PublicAddrString, &addr));
+  EXPECT_EQ(addr.ToString(), kIPv6PublicAddrString);
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  addr2 = IPAddress(kIPv4MappedRFC1918Addr);
+  EXPECT_TRUE(IPFromString(kIPv4MappedV4StyleAddrString, &addr));
+  EXPECT_PRED2(AreEqual, addr, addr2);
+
+  // Broken cases, should set addr to AF_UNSPEC.
+  EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString1);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString2);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString3);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString4);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString5);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString6);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString1);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString2);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString3);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString4);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString5);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString6);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString7);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString8);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString9);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString10);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString11);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString12);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString13);
+  EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString14);
+}
+
+TEST(IPAddressTest, TestIsPrivate) {
+  EXPECT_FALSE(IPIsPrivate(IPAddress(INADDR_ANY)));
+  EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4PublicAddr)));
+  EXPECT_FALSE(IPIsPrivate(IPAddress(in6addr_any)));
+  EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv6PublicAddr)));
+  EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4MappedAnyAddr)));
+  EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4MappedPublicAddr)));
+
+  EXPECT_TRUE(IPIsPrivate(IPAddress(kIPv4RFC1918Addr)));
+  EXPECT_TRUE(IPIsPrivate(IPAddress(INADDR_LOOPBACK)));
+  EXPECT_TRUE(IPIsPrivate(IPAddress(in6addr_loopback)));
+  EXPECT_TRUE(IPIsPrivate(IPAddress(kIPv6LinkLocalAddr)));
+}
+
+TEST(IPAddressTest, TestIsLoopback) {
+  EXPECT_FALSE(IPIsLoopback(IPAddress(INADDR_ANY)));
+  EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4PublicAddr)));
+  EXPECT_FALSE(IPIsLoopback(IPAddress(in6addr_any)));
+  EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv6PublicAddr)));
+  EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4MappedAnyAddr)));
+  EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4MappedPublicAddr)));
+
+  EXPECT_TRUE(IPIsLoopback(IPAddress(INADDR_LOOPBACK)));
+  EXPECT_TRUE(IPIsLoopback(IPAddress(in6addr_loopback)));
+}
+
+TEST(IPAddressTest, TestNormalized) {
+  // Check normalizing a ::ffff:a.b.c.d address.
+  IPAddress addr;
+  EXPECT_TRUE(IPFromString(kIPv4MappedV4StyleAddrString, &addr));
+  IPAddress addr2(kIPv4RFC1918Addr);
+  addr = addr.Normalized();
+  EXPECT_EQ(addr2, addr);
+
+  // Check normalizing a ::ffff:aabb:ccdd address.
+  addr = IPAddress(kIPv4MappedPublicAddr);
+  addr2 = IPAddress(kIPv4PublicAddr);
+  addr = addr.Normalized();
+  EXPECT_EQ(addr, addr2);
+
+  // Check that a non-mapped v6 addresses isn't altered.
+  addr = IPAddress(kIPv6PublicAddr);
+  addr2 = IPAddress(kIPv6PublicAddr);
+  addr = addr.Normalized();
+  EXPECT_EQ(addr, addr2);
+
+  // Check that addresses that look a bit like mapped addresses aren't altered
+  EXPECT_TRUE(IPFromString("fe80::ffff:0102:0304", &addr));
+  addr2 = addr;
+  addr = addr.Normalized();
+  EXPECT_EQ(addr, addr2);
+  EXPECT_TRUE(IPFromString("::0102:0304", &addr));
+  addr2 = addr;
+  addr = addr.Normalized();
+  EXPECT_EQ(addr, addr2);
+  // This string should 'work' as an IP address but is not a mapped address,
+  // so it shouldn't change on normalization.
+  EXPECT_TRUE(IPFromString("::192.168.7.1", &addr));
+  addr2 = addr;
+  addr = addr.Normalized();
+  EXPECT_EQ(addr, addr2);
+
+  // Check that v4 addresses aren't altered.
+  addr = IPAddress(htonl(kIPv4PublicAddr));
+  addr2 = IPAddress(htonl(kIPv4PublicAddr));
+  addr = addr.Normalized();
+  EXPECT_EQ(addr, addr2);
+}
+
+TEST(IPAddressTest, TestAsIPv6Address) {
+  IPAddress addr(kIPv4PublicAddr);
+  IPAddress addr2(kIPv4MappedPublicAddr);
+  addr = addr.AsIPv6Address();
+  EXPECT_EQ(addr, addr2);
+
+  addr = IPAddress(kIPv4MappedPublicAddr);
+  addr2 = IPAddress(kIPv4MappedPublicAddr);
+  addr = addr.AsIPv6Address();
+  EXPECT_EQ(addr, addr2);
+
+  addr = IPAddress(kIPv6PublicAddr);
+  addr2 = IPAddress(kIPv6PublicAddr);
+  addr = addr.AsIPv6Address();
+  EXPECT_EQ(addr, addr2);
+}
+
+TEST(IPAddressTest, TestCountIPMaskBits) {
+  IPAddress mask;
+  // IPv4 on byte boundaries
+  EXPECT_PRED2(CheckMaskCount, "255.255.255.255", 32);
+  EXPECT_PRED2(CheckMaskCount, "255.255.255.0", 24);
+  EXPECT_PRED2(CheckMaskCount, "255.255.0.0", 16);
+  EXPECT_PRED2(CheckMaskCount, "255.0.0.0", 8);
+  EXPECT_PRED2(CheckMaskCount, "0.0.0.0", 0);
+
+  // IPv4 not on byte boundaries
+  EXPECT_PRED2(CheckMaskCount, "128.0.0.0", 1);
+  EXPECT_PRED2(CheckMaskCount, "224.0.0.0", 3);
+  EXPECT_PRED2(CheckMaskCount, "255.248.0.0", 13);
+  EXPECT_PRED2(CheckMaskCount, "255.255.224.0", 19);
+  EXPECT_PRED2(CheckMaskCount, "255.255.255.252", 30);
+
+  // V6 on byte boundaries
+  EXPECT_PRED2(CheckMaskCount, "::", 0);
+  EXPECT_PRED2(CheckMaskCount, "ff00::", 8);
+  EXPECT_PRED2(CheckMaskCount, "ffff::", 16);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ff00::", 24);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff::", 32);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ff00::", 40);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff::", 48);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ff00::", 56);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff::", 64);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ff00::", 72);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff::", 80);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff00::", 88);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff::", 96);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0000", 104);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0000", 112);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128);
+
+  // V6 not on byte boundaries.
+  EXPECT_PRED2(CheckMaskCount, "8000::", 1);
+  EXPECT_PRED2(CheckMaskCount, "ff80::", 9);
+  EXPECT_PRED2(CheckMaskCount, "ffff:fe00::", 23);
+  EXPECT_PRED2(CheckMaskCount, "ffff:fffe::", 31);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:e000::", 35);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffe0::", 43);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:f800::", 53);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:fff8::", 61);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:fc00::", 70);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:fffc::", 78);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:8000::", 81);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff80::", 89);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:fe00::", 103);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0000", 111);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00", 118);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc", 126);
+
+  // Non-contiguous ranges. These are kind-of invalid but lets test them
+  // to make sure they don't crash anything or infinite loop or something.
+  EXPECT_PRED2(CheckMaskCount, "217.0.0.0", 2);
+  EXPECT_PRED2(CheckMaskCount, "255.185.0.0", 9);
+  EXPECT_PRED2(CheckMaskCount, "255.255.251.0", 21);
+  EXPECT_PRED2(CheckMaskCount, "255.255.251.255", 21);
+  EXPECT_PRED2(CheckMaskCount, "255.255.254.201", 23);
+  EXPECT_PRED2(CheckMaskCount, "::1", 0);
+  EXPECT_PRED2(CheckMaskCount, "fe80::1", 7);
+  EXPECT_PRED2(CheckMaskCount, "ff80::1", 9);
+  EXPECT_PRED2(CheckMaskCount, "ffff::1", 16);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ff00:1::1", 24);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff::ffff:1", 32);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ff00:1::", 40);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff::ff00", 48);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ff00:1234::", 56);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:0012::ffff", 64);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ff01::", 72);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:7f00::", 80);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff7a::", 88);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:7f00:0000", 96);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff70:0000", 104);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0211", 112);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff7f", 120);
+}
+
+TEST(IPAddressTest, TestTruncateIP) {
+  EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 24, "255.255.255.0");
+  EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 16, "255.255.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 8, "255.0.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "202.67.7.255", 24, "202.67.7.0");
+  EXPECT_PRED3(CheckTruncateIP, "202.129.65.205", 16, "202.129.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "55.25.2.77", 8, "55.0.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "74.128.99.254", 1, "0.0.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "106.55.99.254", 3, "96.0.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "172.167.53.222", 13, "172.160.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "255.255.224.0", 18, "255.255.192.0");
+  EXPECT_PRED3(CheckTruncateIP, "255.255.255.252", 28, "255.255.255.240");
+
+  EXPECT_PRED3(CheckTruncateIP, "fe80:1111:2222:3333:4444:5555:6666:7777", 1,
+               "8000::");
+  EXPECT_PRED3(CheckTruncateIP, "fff0:1111:2222:3333:4444:5555:6666:7777", 9,
+               "ff80::");
+  EXPECT_PRED3(CheckTruncateIP, "ffff:ff80:1111:2222:3333:4444:5555:6666", 23,
+               "ffff:fe00::");
+  EXPECT_PRED3(CheckTruncateIP, "2400:f9af:e456:1111:2222:3333:4444:5555", 35,
+               "2400:f9af:e000::");
+  EXPECT_PRED3(CheckTruncateIP, "9999:1111:2233:4444:5555:6666:7777:8888", 53,
+               "9999:1111:2233:4000::");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 68,
+               "1111:2222:3333:4444:5000::");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 92,
+               "1111:2222:3333:4444:5555:6660::");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 105,
+               "1111:2222:3333:4444:5555:6666:7700::");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 124,
+               "1111:2222:3333:4444:5555:6666:7777:8880");
+
+  // Slightly degenerate cases
+  EXPECT_PRED3(CheckTruncateIP, "202.165.33.127", 32, "202.165.33.127");
+  EXPECT_PRED3(CheckTruncateIP, "235.105.77.12", 0, "0.0.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 128,
+               "1111:2222:3333:4444:5555:6666:7777:8888");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 0,
+               "::");
+}
+}  // namespace talk_base
diff --git a/talk/base/json_unittest.cc b/talk/base/json_unittest.cc
new file mode 100644
index 0000000..a39b944
--- /dev/null
+++ b/talk/base/json_unittest.cc
@@ -0,0 +1,205 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <vector>
+#include "talk/base/gunit.h"
+#include "talk/base/json.h"
+
+static Json::Value in_s("foo");
+static Json::Value in_sn("99");
+static Json::Value in_si("-99");
+static Json::Value in_sb("true");
+static Json::Value in_sd("1.2");
+static Json::Value in_n(12);
+static Json::Value in_i(-12);
+static Json::Value in_u(34U);
+static Json::Value in_b(true);
+static Json::Value in_d(1.2);
+static Json::Value big_sn("12345678901234567890");
+static Json::Value big_si("-12345678901234567890");
+static Json::Value big_u(0xFFFFFFFF);
+static Json::Value bad_a(Json::arrayValue);
+static Json::Value bad_o(Json::objectValue);
+
+TEST(JsonTest, GetString) {
+  std::string out;
+  EXPECT_TRUE(GetStringFromJson(in_s, &out));
+  EXPECT_EQ("foo", out);
+  EXPECT_TRUE(GetStringFromJson(in_sn, &out));
+  EXPECT_EQ("99", out);
+  EXPECT_TRUE(GetStringFromJson(in_si, &out));
+  EXPECT_EQ("-99", out);
+  EXPECT_TRUE(GetStringFromJson(in_i, &out));
+  EXPECT_EQ("-12", out);
+  EXPECT_TRUE(GetStringFromJson(in_n, &out));
+  EXPECT_EQ("12", out);
+  EXPECT_TRUE(GetStringFromJson(in_u, &out));
+  EXPECT_EQ("34", out);
+  EXPECT_TRUE(GetStringFromJson(in_b, &out));
+  EXPECT_EQ("true", out);
+  // Not supported here yet.
+  EXPECT_FALSE(GetStringFromJson(bad_a, &out));
+  EXPECT_FALSE(GetStringFromJson(bad_o, &out));
+}
+
+TEST(JsonTest, GetInt) {
+  int out;
+  EXPECT_TRUE(GetIntFromJson(in_sn, &out));
+  EXPECT_EQ(99, out);
+  EXPECT_TRUE(GetIntFromJson(in_si, &out));
+  EXPECT_EQ(-99, out);
+  EXPECT_TRUE(GetIntFromJson(in_n, &out));
+  EXPECT_EQ(12, out);
+  EXPECT_TRUE(GetIntFromJson(in_i, &out));
+  EXPECT_EQ(-12, out);
+  EXPECT_TRUE(GetIntFromJson(in_u, &out));
+  EXPECT_EQ(34, out);
+  EXPECT_TRUE(GetIntFromJson(in_b, &out));
+  EXPECT_EQ(1, out);
+  EXPECT_FALSE(GetIntFromJson(in_s, &out));
+  EXPECT_FALSE(GetIntFromJson(big_sn, &out));
+  EXPECT_FALSE(GetIntFromJson(big_si, &out));
+  EXPECT_FALSE(GetIntFromJson(big_u, &out));
+  EXPECT_FALSE(GetIntFromJson(bad_a, &out));
+  EXPECT_FALSE(GetIntFromJson(bad_o, &out));
+}
+
+TEST(JsonTest, GetUInt) {
+  unsigned int out;
+  EXPECT_TRUE(GetUIntFromJson(in_sn, &out));
+  EXPECT_EQ(99U, out);
+  EXPECT_TRUE(GetUIntFromJson(in_n, &out));
+  EXPECT_EQ(12U, out);
+  EXPECT_TRUE(GetUIntFromJson(in_u, &out));
+  EXPECT_EQ(34U, out);
+  EXPECT_TRUE(GetUIntFromJson(in_b, &out));
+  EXPECT_EQ(1U, out);
+  EXPECT_TRUE(GetUIntFromJson(big_u, &out));
+  EXPECT_EQ(0xFFFFFFFFU, out);
+  EXPECT_FALSE(GetUIntFromJson(in_s, &out));
+  // TODO: Fail reading negative strings.
+  // EXPECT_FALSE(GetUIntFromJson(in_si, &out));
+  EXPECT_FALSE(GetUIntFromJson(in_i, &out));
+  EXPECT_FALSE(GetUIntFromJson(big_sn, &out));
+  EXPECT_FALSE(GetUIntFromJson(big_si, &out));
+  EXPECT_FALSE(GetUIntFromJson(bad_a, &out));
+  EXPECT_FALSE(GetUIntFromJson(bad_o, &out));
+}
+
+TEST(JsonTest, GetBool) {
+  bool out;
+  EXPECT_TRUE(GetBoolFromJson(in_sb, &out));
+  EXPECT_EQ(true, out);
+  EXPECT_TRUE(GetBoolFromJson(in_n, &out));
+  EXPECT_EQ(true, out);
+  EXPECT_TRUE(GetBoolFromJson(in_i, &out));
+  EXPECT_EQ(true, out);
+  EXPECT_TRUE(GetBoolFromJson(in_u, &out));
+  EXPECT_EQ(true, out);
+  EXPECT_TRUE(GetBoolFromJson(in_b, &out));
+  EXPECT_EQ(true, out);
+  EXPECT_TRUE(GetBoolFromJson(big_u, &out));
+  EXPECT_EQ(true, out);
+  EXPECT_FALSE(GetBoolFromJson(in_s, &out));
+  EXPECT_FALSE(GetBoolFromJson(in_sn, &out));
+  EXPECT_FALSE(GetBoolFromJson(in_si, &out));
+  EXPECT_FALSE(GetBoolFromJson(big_sn, &out));
+  EXPECT_FALSE(GetBoolFromJson(big_si, &out));
+  EXPECT_FALSE(GetBoolFromJson(bad_a, &out));
+  EXPECT_FALSE(GetBoolFromJson(bad_o, &out));
+}
+
+TEST(JsonTest, GetDouble) {
+  double out;
+  EXPECT_TRUE(GetDoubleFromJson(in_sn, &out));
+  EXPECT_EQ(99, out);
+  EXPECT_TRUE(GetDoubleFromJson(in_si, &out));
+  EXPECT_EQ(-99, out);
+  EXPECT_TRUE(GetDoubleFromJson(in_sd, &out));
+  EXPECT_EQ(1.2, out);
+  EXPECT_TRUE(GetDoubleFromJson(in_n, &out));
+  EXPECT_EQ(12, out);
+  EXPECT_TRUE(GetDoubleFromJson(in_i, &out));
+  EXPECT_EQ(-12, out);
+  EXPECT_TRUE(GetDoubleFromJson(in_u, &out));
+  EXPECT_EQ(34, out);
+  EXPECT_TRUE(GetDoubleFromJson(in_b, &out));
+  EXPECT_EQ(1, out);
+  EXPECT_TRUE(GetDoubleFromJson(in_d, &out));
+  EXPECT_EQ(1.2, out);
+  EXPECT_FALSE(GetDoubleFromJson(in_s, &out));
+}
+
+TEST(JsonTest, GetFromArray) {
+  Json::Value a, out;
+  a.append(in_s);
+  a.append(in_i);
+  a.append(in_u);
+  a.append(in_b);
+  EXPECT_TRUE(GetValueFromJsonArray(a, 0, &out));
+  EXPECT_TRUE(GetValueFromJsonArray(a, 3, &out));
+  EXPECT_FALSE(GetValueFromJsonArray(a, 99, &out));
+  EXPECT_FALSE(GetValueFromJsonArray(a, 0xFFFFFFFF, &out));
+}
+
+TEST(JsonTest, GetFromObject) {
+  Json::Value o, out;
+  o["string"] = in_s;
+  o["int"] = in_i;
+  o["uint"] = in_u;
+  o["bool"] = in_b;
+  EXPECT_TRUE(GetValueFromJsonObject(o, "int", &out));
+  EXPECT_TRUE(GetValueFromJsonObject(o, "bool", &out));
+  EXPECT_FALSE(GetValueFromJsonObject(o, "foo", &out));
+  EXPECT_FALSE(GetValueFromJsonObject(o, "", &out));
+}
+
+TEST(JsonTest, VectorToArray) {
+  std::vector<std::string> in;
+  Json::Value out;
+  in.push_back("a");
+  in.push_back("b");
+  in.push_back("c");
+  out = StringVectorToJsonValue(in);
+  EXPECT_EQ(in.size(), out.size());
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) {
+    EXPECT_EQ(in[i], out[i].asString());
+  }
+}
+
+TEST(JsonTest, ArrayToVector) {
+  Json::Value in(Json::arrayValue);
+  std::vector<std::string> out;
+  in.append("a");
+  in.append("b");
+  in.append("c");
+  EXPECT_TRUE(JsonValueToStringVector(in, &out));
+  EXPECT_EQ(in.size(), out.size());
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) {
+    EXPECT_EQ(in[i].asString(), out[i]);
+  }
+}
diff --git a/talk/base/latebindingsymboltable.cc b/talk/base/latebindingsymboltable.cc
index f9d59ab..348265e 100644
--- a/talk/base/latebindingsymboltable.cc
+++ b/talk/base/latebindingsymboltable.cc
@@ -37,7 +37,7 @@
 
 inline static const char *GetDllError() {
 #ifdef LINUX
-  char *err = dlerror();
+  const char *err = dlerror();
   if (err) {
     return err;
   } else {
@@ -75,7 +75,7 @@
                        void **symbol) {
 #ifdef LINUX
   *symbol = dlsym(handle, symbol_name);
-  char *err = dlerror();
+  const char *err = dlerror();
   if (err) {
     LOG(LS_ERROR) << "Error loading symbol " << symbol_name << ": " << err;
     return false;
diff --git a/talk/base/latebindingsymboltable_unittest.cc b/talk/base/latebindingsymboltable_unittest.cc
new file mode 100644
index 0000000..a16423e
--- /dev/null
+++ b/talk/base/latebindingsymboltable_unittest.cc
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef LINUX
+#include <math.h>
+#endif
+
+#include "talk/base/gunit.h"
+#include "talk/base/latebindingsymboltable.h"
+
+namespace talk_base {
+
+#ifdef LINUX
+
+// Some libm symbols for our test.
+#define LIBM_SYMBOLS_LIST \
+  X(acos) \
+  X(sin) \
+  X(tan)
+
+LATE_BINDING_SYMBOL_TABLE_DECLARE_BEGIN(LibmTestSymbolTable)
+#define X(sym) \
+    LATE_BINDING_SYMBOL_TABLE_DECLARE_ENTRY(LibmTestSymbolTable, sym)
+LIBM_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DECLARE_END(LibmTestSymbolTable)
+
+LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(LibmTestSymbolTable, "libm.so.6")
+#define X(sym) \
+    LATE_BINDING_SYMBOL_TABLE_DEFINE_ENTRY(LibmTestSymbolTable, sym)
+LIBM_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DEFINE_END(LibmTestSymbolTable)
+
+#define LATE(sym) \
+  LATESYM_GET(LibmTestSymbolTable, &table, sym)
+
+TEST(LateBindingSymbolTable, libm) {
+  LibmTestSymbolTable table;
+  EXPECT_FALSE(table.IsLoaded());
+  ASSERT_TRUE(table.Load());
+  EXPECT_TRUE(table.IsLoaded());
+  EXPECT_EQ(LATE(acos)(0.5), acos(0.5));
+  EXPECT_EQ(LATE(sin)(0.5), sin(0.5));
+  EXPECT_EQ(LATE(tan)(0.5), tan(0.5));
+  // It would be nice to check that the addresses are the same, but the nature
+  // of dynamic linking and relocation makes them actually be different.
+  table.Unload();
+  EXPECT_FALSE(table.IsLoaded());
+}
+
+#else
+#error Not implemented
+#endif
+
+}  // namespace talk_base
diff --git a/talk/base/libdbusglibsymboltable.cc b/talk/base/libdbusglibsymboltable.cc
new file mode 100644
index 0000000..3548a7c
--- /dev/null
+++ b/talk/base/libdbusglibsymboltable.cc
@@ -0,0 +1,41 @@
+/*
+ * 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/libdbusglibsymboltable.h"
+
+namespace talk_base {
+
+LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(LibDBusGlibSymbolTable,
+                                       "libdbus-glib-1.so")
+#define X(sym) \
+    LATE_BINDING_SYMBOL_TABLE_DEFINE_ENTRY(LibDBusGlibSymbolTable, sym)
+LIBDBUS_GLIB_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DEFINE_END(LibDBusGlibSymbolTable)
+
+}  // namespace talk_base
+
diff --git a/talk/base/libdbusglibsymboltable.h b/talk/base/libdbusglibsymboltable.h
new file mode 100644
index 0000000..9aad8a5
--- /dev/null
+++ b/talk/base/libdbusglibsymboltable.h
@@ -0,0 +1,85 @@
+/*
+ * 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_LIBDBUSGLIBSYMBOLTABLE_H_
+#define TALK_BASE_LIBDBUSGLIBSYMBOLTABLE_H_
+
+#include "talk/base/latebindingsymboltable.h"
+
+namespace talk_base {
+
+// The libdbus-glib symbols we need, as an X-Macro list.
+// This list must contain precisely every libdbus-glib function that is used in
+// dbus.cc.
+#define LIBDBUS_GLIB_SYMBOLS_LIST \
+  X(dbus_bus_add_match) \
+  X(dbus_connection_add_filter) \
+  X(dbus_connection_close) \
+  X(dbus_connection_remove_filter) \
+  X(dbus_connection_set_exit_on_disconnect) \
+  X(dbus_g_bus_get) \
+  X(dbus_g_bus_get_private) \
+  X(dbus_g_connection_get_connection) \
+  X(dbus_g_connection_unref) \
+  X(dbus_g_thread_init) \
+  X(dbus_message_get_interface) \
+  X(dbus_message_get_member) \
+  X(dbus_message_get_path) \
+  X(dbus_message_get_type) \
+  X(dbus_message_iter_get_arg_type) \
+  X(dbus_message_iter_get_basic) \
+  X(dbus_message_iter_init) \
+  X(dbus_message_ref) \
+  X(dbus_message_unref) \
+  X(g_free) \
+  X(g_idle_source_new) \
+  X(g_main_context_new) \
+  X(g_main_context_push_thread_default) \
+  X(g_main_context_unref) \
+  X(g_main_loop_new) \
+  X(g_main_loop_quit) \
+  X(g_main_loop_run) \
+  X(g_main_loop_unref) \
+  X(g_object_unref) \
+  X(g_source_attach) \
+  X(g_source_destroy) \
+  X(g_source_set_callback) \
+  X(g_source_unref) \
+  X(g_thread_init) \
+  X(g_type_init)
+
+LATE_BINDING_SYMBOL_TABLE_DECLARE_BEGIN(LibDBusGlibSymbolTable)
+#define X(sym) \
+    LATE_BINDING_SYMBOL_TABLE_DECLARE_ENTRY(LibDBusGlibSymbolTable, sym)
+LIBDBUS_GLIB_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DECLARE_END(LibDBusGlibSymbolTable)
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_LIBDBUSGLIBSYMBOLTABLE_H_
+
diff --git a/talk/base/linux.cc b/talk/base/linux.cc
index 9303f4f..a4434f5 100644
--- a/talk/base/linux.cc
+++ b/talk/base/linux.cc
@@ -135,8 +135,23 @@
 bool ProcCpuInfo::GetCpuFamily(int* id) {
   int cpu_family = 0;
 
+#if defined(__arm__)
+  // On some ARM platforms, there is no 'cpu family' in '/proc/cpuinfo'. But
+  // there is 'CPU Architecture' which can be used as 'cpu family'.
+  // See http://en.wikipedia.org/wiki/ARM_architecture for a good list of
+  // ARM cpu families, architectures, and their mappings.
+  // There may be multiple sessions that aren't per-processor. We need to scan
+  // through each session until we find the first 'CPU architecture'.
+  size_t section_count = sections_.size();
+  for (size_t i = 0; i < section_count; ++i) {
+    if (GetSectionIntValue(i, "CPU architecture", &cpu_family)) {
+      // We returns the first one (if there are multiple entries).
+      break;
+    };
+  }
+#else
   GetSectionIntValue(0, "cpu family", &cpu_family);
-
+#endif
   if (id) {
     *id = cpu_family;
   }
diff --git a/talk/base/linux_unittest.cc b/talk/base/linux_unittest.cc
new file mode 100644
index 0000000..6e1a8a9
--- /dev/null
+++ b/talk/base/linux_unittest.cc
@@ -0,0 +1,119 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include "talk/base/linux.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "testing/base/gunit.h"
+
+namespace talk_base {
+
+// These tests running on ARM are fairly specific to the output of the tegra2
+// ARM processor, and so may fail on other ARM-based systems.
+TEST(ProcCpuInfo, GetProcInfo) {
+  ProcCpuInfo proc_info;
+  EXPECT_TRUE(proc_info.LoadFromSystem());
+
+  int out_cpus = 0;
+  EXPECT_TRUE(proc_info.GetNumCpus(&out_cpus));
+  LOG(LS_INFO) << "GetNumCpus: " << out_cpus;
+  EXPECT_GT(out_cpus, 0);
+
+  int out_cpus_phys = 0;
+  EXPECT_TRUE(proc_info.GetNumPhysicalCpus(&out_cpus_phys));
+  LOG(LS_INFO) << "GetNumPhysicalCpus: " << out_cpus_phys;
+  EXPECT_GT(out_cpus_phys, 0);
+  EXPECT_LE(out_cpus_phys, out_cpus);
+
+  int out_family = 0;
+  EXPECT_TRUE(proc_info.GetCpuFamily(&out_family));
+  LOG(LS_INFO) << "cpu family: " << out_family;
+  EXPECT_GE(out_family, 4);
+
+#if defined(__arm__)
+  std::string out_processor;
+  EXPECT_TRUE(proc_info.GetSectionStringValue(0, "Processor", &out_processor));
+  LOG(LS_INFO) << "Processor: " << out_processor;
+  EXPECT_NE(std::string::npos, out_processor.find("ARM"));
+
+  // Most other info, such as model, stepping, vendor, etc.
+  // is missing on ARM systems.
+#else
+  int out_model = 0;
+  EXPECT_TRUE(proc_info.GetSectionIntValue(0, "model", &out_model));
+  LOG(LS_INFO) << "model: " << out_model;
+
+  int out_stepping = 0;
+  EXPECT_TRUE(proc_info.GetSectionIntValue(0, "stepping", &out_stepping));
+  LOG(LS_INFO) << "stepping: " << out_stepping;
+
+  int out_processor = 0;
+  EXPECT_TRUE(proc_info.GetSectionIntValue(0, "processor", &out_processor));
+  LOG(LS_INFO) << "processor: " << out_processor;
+  EXPECT_EQ(0, out_processor);
+
+  std::string out_str;
+  EXPECT_TRUE(proc_info.GetSectionStringValue(0, "vendor_id", &out_str));
+  LOG(LS_INFO) << "vendor_id: " << out_str;
+  EXPECT_FALSE(out_str.empty());
+#endif
+}
+
+TEST(ConfigParser, ParseConfig) {
+  ConfigParser parser;
+  MemoryStream *test_stream = new MemoryStream(
+      "Key1: Value1\n"
+      "Key2\t: Value2\n"
+      "Key3:Value3\n"
+      "\n"
+      "Key1:Value1\n");
+  ConfigParser::MapVector key_val_pairs;
+  parser.Attach(test_stream);
+  EXPECT_EQ(true, parser.Parse(&key_val_pairs));
+  EXPECT_EQ(2U, key_val_pairs.size());
+  EXPECT_EQ("Value1", key_val_pairs[0]["Key1"]);
+  EXPECT_EQ("Value2", key_val_pairs[0]["Key2"]);
+  EXPECT_EQ("Value3", key_val_pairs[0]["Key3"]);
+  EXPECT_EQ("Value1", key_val_pairs[1]["Key1"]);
+  key_val_pairs.clear();
+  EXPECT_EQ(true, parser.Open("/proc/cpuinfo"));
+  EXPECT_EQ(true, parser.Parse(&key_val_pairs));
+}
+
+TEST(ReadLinuxLsbRelease, ReturnsSomething) {
+  std::string str = ReadLinuxLsbRelease();
+  // ChromeOS don't have lsb_release
+  // EXPECT_FALSE(str.empty());
+}
+
+TEST(ReadLinuxUname, ReturnsSomething) {
+  std::string str = ReadLinuxUname();
+  EXPECT_FALSE(str.empty());
+}
+
+}  // namespace talk_base
diff --git a/talk/base/linuxfdwalk.c b/talk/base/linuxfdwalk.c
new file mode 100644
index 0000000..4179f41
--- /dev/null
+++ b/talk/base/linuxfdwalk.c
@@ -0,0 +1,98 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "talk/base/linuxfdwalk.h"
+
+// Parses a file descriptor number in base 10, requiring the strict format used
+// in /proc/*/fd. Returns the value, or -1 if not a valid string.
+static int parse_fd(const char *s) {
+  if (!*s) {
+    // Empty string is invalid.
+    return -1;
+  }
+  int val = 0;
+  do {
+    if (*s < '0' || *s > '9') {
+      // Non-numeric characters anywhere are invalid.
+      return -1;
+    }
+    int digit = *s++ - '0';
+    val = val * 10 + digit;
+  } while (*s);
+  return val;
+}
+
+int fdwalk(void (*func)(void *, int), void *opaque) {
+  DIR *dir = opendir("/proc/self/fd");
+  if (!dir) {
+    return -1;
+  }
+  int opendirfd = dirfd(dir);
+  int parse_errors = 0;
+  struct dirent *ent;
+  // Have to clear errno to distinguish readdir() completion from failure.
+  while (errno = 0, (ent = readdir(dir)) != NULL) {
+    if (strcmp(ent->d_name, ".") == 0 ||
+        strcmp(ent->d_name, "..") == 0) {
+      continue;
+    }
+    // We avoid atoi or strtol because those are part of libc and they involve
+    // locale stuff, which is probably not safe from a post-fork context in a
+    // multi-threaded app.
+    int fd = parse_fd(ent->d_name);
+    if (fd < 0) {
+      parse_errors = 1;
+      continue;
+    }
+    if (fd != opendirfd) {
+      (*func)(opaque, fd);
+    }
+  }
+  int saved_errno = errno;
+  if (closedir(dir) < 0) {
+    if (!saved_errno) {
+      // Return the closedir error.
+      return -1;
+    }
+    // Else ignore it because we have a more relevant error to return.
+  }
+  if (saved_errno) {
+    errno = saved_errno;
+    return -1;
+  } else if (parse_errors) {
+    errno = EBADF;
+    return -1;
+  } else {
+    return 0;
+  }
+}
diff --git a/talk/base/linuxfdwalk.h b/talk/base/linuxfdwalk.h
new file mode 100644
index 0000000..ea039bf
--- /dev/null
+++ b/talk/base/linuxfdwalk.h
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_LINUXFDWALK_H_
+#define TALK_BASE_LINUXFDWALK_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Linux port of SunOS's fdwalk(3) call. It loops over all open file descriptors
+// and calls func on each one. Additionally, it is safe to use from the child
+// of a fork that hasn't exec'ed yet, so you can use it to close all open file
+// descriptors prior to exec'ing a daemon.
+// The return value is 0 if successful, or else -1 and errno is set. The
+// possible errors include any error that can be returned by opendir(),
+// readdir(), or closedir(), plus EBADF if there are problems parsing the
+// contents of /proc/self/fd.
+// The file descriptors that are enumerated will not include the file descriptor
+// used for the enumeration itself.
+int fdwalk(void (*func)(void *, int), void *opaque);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // TALK_BASE_LINUXFDWALK_H_
diff --git a/talk/base/linuxfdwalk_unittest.cc b/talk/base/linuxfdwalk_unittest.cc
new file mode 100644
index 0000000..ff14b66
--- /dev/null
+++ b/talk/base/linuxfdwalk_unittest.cc
@@ -0,0 +1,92 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <set>
+#include <sstream>
+
+#include "talk/base/gunit.h"
+#include "talk/base/linuxfdwalk.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+static const int kArbitraryLargeFdNumber = 424;
+
+static void FdCheckVisitor(void *data, int fd) {
+  std::set<int> *fds = static_cast<std::set<int> *>(data);
+  EXPECT_EQ(1U, fds->erase(fd));
+}
+
+static void FdEnumVisitor(void *data, int fd) {
+  std::set<int> *fds = static_cast<std::set<int> *>(data);
+  EXPECT_TRUE(fds->insert(fd).second);
+}
+
+// Checks that the set of open fds is exactly the given list.
+static void CheckOpenFdList(std::set<int> fds) {
+  EXPECT_EQ(0, fdwalk(&FdCheckVisitor, &fds));
+  EXPECT_EQ(0U, fds.size());
+}
+
+static void GetOpenFdList(std::set<int> *fds) {
+  fds->clear();
+  EXPECT_EQ(0, fdwalk(&FdEnumVisitor, fds));
+}
+
+TEST(LinuxFdWalk, TestFdWalk) {
+  std::set<int> fds;
+  GetOpenFdList(&fds);
+  std::ostringstream str;
+  // I have observed that the open set when starting a test is [0, 6]. Leaked
+  // fds would change that, but so can (e.g.) running under a debugger, so we
+  // can't really do an EXPECT. :(
+  str << "File descriptors open in test executable:";
+  for (std::set<int>::const_iterator i = fds.begin(); i != fds.end(); ++i) {
+    str << " " << *i;
+  }
+  LOG(LS_INFO) << str.str();
+  // Open some files.
+  int fd1 = open("/dev/null", O_RDONLY);
+  EXPECT_LE(0, fd1);
+  int fd2 = open("/dev/null", O_WRONLY);
+  EXPECT_LE(0, fd2);
+  int fd3 = open("/dev/null", O_RDWR);
+  EXPECT_LE(0, fd3);
+  int fd4 = dup2(fd3, kArbitraryLargeFdNumber);
+  EXPECT_LE(0, fd4);
+  EXPECT_TRUE(fds.insert(fd1).second);
+  EXPECT_TRUE(fds.insert(fd2).second);
+  EXPECT_TRUE(fds.insert(fd3).second);
+  EXPECT_TRUE(fds.insert(fd4).second);
+  CheckOpenFdList(fds);
+  EXPECT_EQ(0, close(fd1));
+  EXPECT_EQ(0, close(fd2));
+  EXPECT_EQ(0, close(fd3));
+  EXPECT_EQ(0, close(fd4));
+}
diff --git a/talk/base/linuxwindowpicker.cc b/talk/base/linuxwindowpicker.cc
new file mode 100644
index 0000000..250c1db
--- /dev/null
+++ b/talk/base/linuxwindowpicker.cc
@@ -0,0 +1,735 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/linuxwindowpicker.h"
+
+#include <math.h>
+#include <string.h>
+
+#include <string>
+
+#include <X11/Xatom.h>
+#include <X11/extensions/Xcomposite.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/Xutil.h>
+
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+// Stupid X11.  It seems none of the synchronous returns codes from X11 calls
+// are meaningful unless an asynchronous error handler is configured.  This
+// RAII class registers and unregisters an X11 error handler.
+class XErrorSuppressor {
+ public:
+  explicit XErrorSuppressor(Display* display)
+      : display_(display), original_error_handler_(NULL) {
+    SuppressX11Errors();
+  }
+  ~XErrorSuppressor() {
+    UnsuppressX11Errors();
+  }
+
+ private:
+  static int ErrorHandler(Display* display, XErrorEvent* e) {
+    char buf[256];
+    XGetErrorText(display, e->error_code, buf, sizeof buf);
+    LOG(LS_WARNING) << "Received X11 error \"" << buf << "\" for request code "
+                    << static_cast<unsigned int>(e->request_code);
+    return 0;
+  }
+
+  void SuppressX11Errors() {
+    XFlush(display_);
+    XSync(display_, False);
+    original_error_handler_ = XSetErrorHandler(&ErrorHandler);
+  }
+
+  void UnsuppressX11Errors() {
+    XFlush(display_);
+    XSync(display_, False);
+    XErrorHandler handler = XSetErrorHandler(original_error_handler_);
+    if (handler != &ErrorHandler) {
+      LOG(LS_WARNING) << "Unbalanced XSetErrorHandler() calls detected. "
+                      << "Final error handler may not be what you expect!";
+    }
+    original_error_handler_ = NULL;
+  }
+
+  Display* display_;
+  XErrorHandler original_error_handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(XErrorSuppressor);
+};
+
+// Hiding all X11 specifics inside its own class. This to avoid
+// conflicts between talk and X11 header declarations.
+class XWindowEnumerator {
+ public:
+  XWindowEnumerator()
+      : display_(NULL),
+        has_composite_extension_(false),
+        has_render_extension_(false) {
+  }
+
+  ~XWindowEnumerator() {
+    if (display_ != NULL) {
+      XCloseDisplay(display_);
+    }
+  }
+
+  bool Init() {
+    if (display_ != NULL) {
+      // Already initialized.
+      return true;
+    }
+    display_ = XOpenDisplay(NULL);
+    if (display_ == NULL) {
+      LOG(LS_ERROR) << "Failed to open display.";
+      return false;
+    }
+
+    XErrorSuppressor error_suppressor(display_);
+
+    wm_state_ = XInternAtom(display_, "WM_STATE", True);
+    net_wm_icon_ = XInternAtom(display_, "_NET_WM_ICON", False);
+
+    int event_base, error_base, major_version, minor_version;
+    if (XCompositeQueryExtension(display_, &event_base, &error_base) &&
+        XCompositeQueryVersion(display_, &major_version, &minor_version) &&
+        // XCompositeNameWindowPixmap() requires version 0.2
+        (major_version > 0 || minor_version >= 2)) {
+      has_composite_extension_ = true;
+    } else {
+      LOG(LS_INFO) << "Xcomposite extension not available or too old.";
+    }
+
+    if (XRenderQueryExtension(display_, &event_base, &error_base) &&
+        XRenderQueryVersion(display_, &major_version, &minor_version) &&
+        // XRenderSetPictureTransform() requires version 0.6
+        (major_version > 0 || minor_version >= 6)) {
+      has_render_extension_ = true;
+    } else {
+      LOG(LS_INFO) << "Xrender extension not available or too old.";
+    }
+    return true;
+  }
+
+  bool EnumerateWindows(WindowDescriptionList* descriptions) {
+    if (!Init()) {
+      return false;
+    }
+    XErrorSuppressor error_suppressor(display_);
+    int num_screens = XScreenCount(display_);
+    bool result = false;
+    for (int i = 0; i < num_screens; ++i) {
+      if (EnumerateScreenWindows(descriptions, i)) {
+        // We know we succeded on at least one screen.
+        result = true;
+      }
+    }
+    return result;
+  }
+
+  bool EnumerateDesktops(DesktopDescriptionList* descriptions) {
+    if (!Init()) {
+      return false;
+    }
+    XErrorSuppressor error_suppressor(display_);
+    int num_screens = XScreenCount(display_);
+    for (int i = 0; i < num_screens; ++i) {
+      Window root_window = XRootWindow(display_, i);
+      DesktopId id(DesktopId(root_window, i));
+      // TODO: Figure out an appropriate desktop title.
+      DesktopDescription desc(id, "");
+      descriptions->push_back(desc);
+    }
+    return num_screens > 0;
+  }
+
+  bool IsVisible(const WindowId& id) {
+    if (!Init()) {
+      return false;
+    }
+    XErrorSuppressor error_suppressor(display_);
+    XWindowAttributes attr;
+    if (!XGetWindowAttributes(display_, id.id(), &attr)) {
+      LOG(LS_ERROR) << "XGetWindowAttributes() failed";
+      return false;
+    }
+    return attr.map_state == IsViewable;
+  }
+
+  bool MoveToFront(const WindowId& id) {
+    if (!Init()) {
+      return false;
+    }
+    XErrorSuppressor error_suppressor(display_);
+    unsigned int num_children;
+    Window* children;
+    Window parent;
+    Window root;
+
+    // Find root window to pass event to.
+    int status = XQueryTree(display_, id.id(), &root, &parent, &children,
+                            &num_children);
+    if (status == 0) {
+      LOG(LS_WARNING) << "Failed to query for child windows.";
+      return false;
+    }
+    if (children != NULL) {
+      XFree(children);
+    }
+
+    // Move the window to front.
+    XRaiseWindow(display_, id.id());
+
+    // Some window managers (e.g., metacity in GNOME) consider it illegal to
+    // raise a window without also giving it input focus with
+    // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough.
+    Atom atom = XInternAtom(display_, "_NET_ACTIVE_WINDOW", True);
+    if (atom != None) {
+      XEvent xev;
+      long event_mask;
+
+      xev.xclient.type = ClientMessage;
+      xev.xclient.serial = 0;
+      xev.xclient.send_event = True;
+      xev.xclient.window = id.id();
+      xev.xclient.message_type = atom;
+
+      // The format member is set to 8, 16, or 32 and specifies whether the
+      // data should be viewed as a list of bytes, shorts, or longs.
+      xev.xclient.format = 32;
+
+      xev.xclient.data.l[0] = 0;
+      xev.xclient.data.l[1] = 0;
+      xev.xclient.data.l[2] = 0;
+      xev.xclient.data.l[3] = 0;
+      xev.xclient.data.l[4] = 0;
+
+      event_mask = SubstructureRedirectMask | SubstructureNotifyMask;
+
+      XSendEvent(display_, root, False, event_mask, &xev);
+    }
+    XFlush(display_);
+    return true;
+  }
+
+  uint8* GetWindowIcon(const WindowId& id, int* width, int* height) {
+    if (!Init()) {
+      return NULL;
+    }
+    XErrorSuppressor error_suppressor(display_);
+    Atom ret_type;
+    int format;
+    unsigned long length, bytes_after, size;
+    unsigned char* data = NULL;
+
+    // Find out the size of the icon data.
+    if (XGetWindowProperty(
+            display_, id.id(), net_wm_icon_, 0, 0, False, XA_CARDINAL,
+            &ret_type, &format, &length, &size, &data) == Success &&
+        data) {
+      XFree(data);
+    } else {
+      LOG(LS_ERROR) << "Failed to get size of the icon.";
+      return NULL;
+    }
+    // Get the icon data, the format is one uint32 each for width and height,
+    // followed by the actual pixel data.
+    if (size >= 2 &&
+        XGetWindowProperty(
+            display_, id.id(), net_wm_icon_, 0, size, False, XA_CARDINAL,
+            &ret_type, &format, &length, &bytes_after, &data) == Success &&
+        data) {
+      uint32* data_ptr = reinterpret_cast<uint32*>(data);
+      int w, h;
+      w = data_ptr[0];
+      h = data_ptr[1];
+      if (size < static_cast<unsigned long>(w * h + 2)) {
+        XFree(data);
+        LOG(LS_ERROR) << "Not a vaild icon.";
+        return NULL;
+      }
+      uint8* rgba =
+          ArgbToRgba(&data_ptr[2], 0, 0, w, h, w, h, true);
+      XFree(data);
+      *width = w;
+      *height = h;
+      return rgba;
+    } else {
+      LOG(LS_ERROR) << "Failed to get window icon data.";
+      return NULL;
+    }
+  }
+
+  uint8* GetWindowThumbnail(const WindowId& id, int width, int height) {
+    if (!Init()) {
+      return NULL;
+    }
+
+    if (!has_composite_extension_) {
+      // Without the Xcomposite extension we would only get a good thumbnail if
+      // the whole window is visible on screen and not covered by any
+      // other window. This is not something we want so instead, just
+      // bail out.
+      LOG(LS_INFO) << "No Xcomposite extension detected.";
+      return NULL;
+    }
+    XErrorSuppressor error_suppressor(display_);
+
+    Window root;
+    int x;
+    int y;
+    unsigned int src_width;
+    unsigned int src_height;
+    unsigned int border_width;
+    unsigned int depth;
+
+    // In addition to needing X11 server-side support for Xcomposite, it
+    // actually needs to be turned on for this window in order to get a good
+    // thumbnail. If the user has modern hardware/drivers but isn't using a
+    // compositing window manager, that won't be the case. We could
+    // automatically turn it on for all windows here so that we can get
+    // thumbnails, but the transition is visually ugly, so instead we just want
+    // to detect this case and bail out. To do so, we attempt to retrieve the
+    // backing pixmap for the window, which will only exist if the window has
+    // been redirected to offscreen drawing by a compositing window manager. If
+    // it doesn't exist, the calls will raise errors, so we must suppress errors
+    // to prevent libX11 from aborting the program.
+    Pixmap src_pixmap = XCompositeNameWindowPixmap(display_, id.id());
+    if (!src_pixmap) {
+      // Even if the backing pixmap doesn't exist, this still should have
+      // succeeded and returned a valid handle (it just wouldn't be a handle to
+      // anything). So this is a real error path.
+      LOG(LS_ERROR) << "XCompositeNameWindowPixmap() failed";
+      return NULL;
+    }
+    if (!XGetGeometry(display_, src_pixmap, &root, &x, &y,
+                      &src_width, &src_height, &border_width,
+                      &depth)) {
+      // If the window does not actually have a backing pixmap, this is the path
+      // that will "fail", so it's a warning rather than an error.
+      LOG(LS_WARNING) << "XGetGeometry() failed (probably composite is not in "
+                      << "use)";
+      XFreePixmap(display_, src_pixmap);
+      return NULL;
+    }
+
+    // If we get to here, then composite is in use for this window and it has a
+    // valid backing pixmap.
+
+    XWindowAttributes attr;
+    if (!XGetWindowAttributes(display_, id.id(), &attr)) {
+      LOG(LS_ERROR) << "XGetWindowAttributes() failed";
+      XFreePixmap(display_, src_pixmap);
+      return NULL;
+    }
+
+    uint8* data = GetDrawableThumbnail(src_pixmap,
+                                       attr.visual,
+                                       src_width,
+                                       src_height,
+                                       width,
+                                       height);
+    XFreePixmap(display_, src_pixmap);
+    return data;
+  }
+
+  int GetNumDesktops() {
+    if (!Init()) {
+      return -1;
+    }
+
+    return XScreenCount(display_);
+  }
+
+  uint8* GetDesktopThumbnail(const DesktopId& id, int width, int height) {
+    if (!Init()) {
+      return NULL;
+    }
+    XErrorSuppressor error_suppressor(display_);
+
+    Window root_window = id.id();
+    XWindowAttributes attr;
+    if (!XGetWindowAttributes(display_, root_window, &attr)) {
+      LOG(LS_ERROR) << "XGetWindowAttributes() failed";
+      return NULL;
+    }
+
+    return GetDrawableThumbnail(root_window,
+                                attr.visual,
+                                attr.width,
+                                attr.height,
+                                width,
+                                height);
+  }
+
+ private:
+  uint8* GetDrawableThumbnail(Drawable src_drawable,
+                              Visual* visual,
+                              int src_width,
+                              int src_height,
+                              int dst_width,
+                              int dst_height) {
+    if (!has_render_extension_) {
+      // Without the Xrender extension we would have to read the full window and
+      // scale it down in our process. Xrender is over a decade old so we aren't
+      // going to expend effort to support that situation. We still need to
+      // check though because probably some virtual VNC displays are in this
+      // category.
+      LOG(LS_INFO) << "No Xrender extension detected.";
+      return NULL;
+    }
+
+    XRenderPictFormat* format = XRenderFindVisualFormat(display_,
+                                                        visual);
+    if (!format) {
+      LOG(LS_ERROR) << "XRenderFindVisualFormat() failed";
+      return NULL;
+    }
+
+    // Create a picture to reference the window pixmap.
+    XRenderPictureAttributes pa;
+    pa.subwindow_mode = IncludeInferiors;  // Don't clip child widgets
+    Picture src = XRenderCreatePicture(display_,
+                                       src_drawable,
+                                       format,
+                                       CPSubwindowMode,
+                                       &pa);
+    if (!src) {
+      LOG(LS_ERROR) << "XRenderCreatePicture() failed";
+      return NULL;
+    }
+
+    // Create a picture to reference the destination pixmap.
+    Pixmap dst_pixmap = XCreatePixmap(display_,
+                                      src_drawable,
+                                      dst_width,
+                                      dst_height,
+                                      format->depth);
+    if (!dst_pixmap) {
+      LOG(LS_ERROR) << "XCreatePixmap() failed";
+      XRenderFreePicture(display_, src);
+      return NULL;
+    }
+
+    Picture dst = XRenderCreatePicture(display_, dst_pixmap, format, 0, NULL);
+    if (!dst) {
+      LOG(LS_ERROR) << "XRenderCreatePicture() failed";
+      XFreePixmap(display_, dst_pixmap);
+      XRenderFreePicture(display_, src);
+      return NULL;
+    }
+
+    // Clear the background.
+    XRenderColor transparent = {0};
+    XRenderFillRectangle(display_,
+                         PictOpSrc,
+                         dst,
+                         &transparent,
+                         0,
+                         0,
+                         dst_width,
+                         dst_height);
+
+    // Calculate how much we need to scale the image.
+    double scale_x = static_cast<double>(dst_width) /
+        static_cast<double>(src_width);
+    double scale_y = static_cast<double>(dst_height) /
+        static_cast<double>(src_height);
+    double scale = talk_base::_min(scale_y, scale_x);
+
+    int scaled_width = round(src_width * scale);
+    int scaled_height = round(src_height * scale);
+
+    // Render the thumbnail centered on both axis.
+    int centered_x = (dst_width - scaled_width) / 2;
+    int centered_y = (dst_height - scaled_height) / 2;
+
+    // Scaling matrix
+    XTransform xform = { {
+        { XDoubleToFixed(1), XDoubleToFixed(0), XDoubleToFixed(0) },
+        { XDoubleToFixed(0), XDoubleToFixed(1), XDoubleToFixed(0) },
+        { XDoubleToFixed(0), XDoubleToFixed(0), XDoubleToFixed(scale) }
+        } };
+    XRenderSetPictureTransform(display_, src, &xform);
+
+    // Apply filter to smooth out the image.
+    XRenderSetPictureFilter(display_, src, FilterBest, NULL, 0);
+
+    // Render the image to the destination picture.
+    XRenderComposite(display_,
+                     PictOpSrc,
+                     src,
+                     None,
+                     dst,
+                     0,
+                     0,
+                     0,
+                     0,
+                     centered_x,
+                     centered_y,
+                     scaled_width,
+                     scaled_height);
+
+    // Get the pixel data from the X server. TODO: XGetImage
+    // might be slow here, compare with ShmGetImage.
+    XImage* image = XGetImage(display_,
+                              dst_pixmap,
+                              0,
+                              0,
+                              dst_width,
+                              dst_height,
+                              AllPlanes, ZPixmap);
+    uint8* data = ArgbToRgba(reinterpret_cast<uint32*>(image->data),
+                             centered_x,
+                             centered_y,
+                             scaled_width,
+                             scaled_height,
+                             dst_width,
+                             dst_height,
+                             false);
+    XDestroyImage(image);
+    XRenderFreePicture(display_, dst);
+    XFreePixmap(display_, dst_pixmap);
+    XRenderFreePicture(display_, src);
+    return data;
+  }
+
+  uint8* ArgbToRgba(uint32* argb_data, int x, int y, int w, int h,
+                    int stride_x, int stride_y, bool has_alpha) {
+    uint8* p;
+    int len = stride_x * stride_y * 4;
+    uint8* data = new uint8[len];
+    memset(data, 0, len);
+    p = data + 4 * (y * stride_x + x);
+    for (int i = 0; i < h; ++i) {
+      for (int j = 0; j < w; ++j) {
+        uint32 argb;
+        uint32 rgba;
+        argb = argb_data[stride_x * (y + i) + x + j];
+        rgba = (argb << 8) | (argb >> 24);
+        *p = rgba >> 24;
+        ++p;
+        *p = (rgba >> 16) & 0xff;
+        ++p;
+        *p = (rgba >> 8) & 0xff;
+        ++p;
+        *p = has_alpha ? rgba & 0xFF : 0xFF;
+        ++p;
+      }
+      p += (stride_x - w) * 4;
+    }
+    return data;
+  }
+
+  bool EnumerateScreenWindows(WindowDescriptionList* descriptions, int screen) {
+    Window parent;
+    Window *children;
+    int status;
+    unsigned int num_children;
+    Window root_window = XRootWindow(display_, screen);
+    status = XQueryTree(display_, root_window, &root_window, &parent, &children,
+                        &num_children);
+    if (status == 0) {
+      LOG(LS_ERROR) << "Failed to query for child windows.";
+      return false;
+    }
+    for (unsigned int i = 0; i < num_children; ++i) {
+      // Iterate in reverse order to display windows from front to back.
+      Window app_window = GetApplicationWindow(children[num_children - 1 - i]);
+      if (app_window &&
+          !LinuxWindowPicker::IsDesktopElement(display_, app_window)) {
+        std::string title;
+        if (GetWindowTitle(app_window, &title)) {
+          WindowId id(app_window);
+          WindowDescription desc(id, title);
+          descriptions->push_back(desc);
+        }
+      }
+    }
+    if (children != NULL) {
+      XFree(children);
+    }
+    return true;
+  }
+
+  bool GetWindowTitle(Window window, std::string* title) {
+    int status;
+    bool result = false;
+    XTextProperty window_name;
+    window_name.value = NULL;
+    if (window) {
+      status = XGetWMName(display_, window, &window_name);
+      if (status && window_name.value && window_name.nitems) {
+        int cnt;
+        char **list = NULL;
+        status = Xutf8TextPropertyToTextList(display_, &window_name, &list,
+                                             &cnt);
+        if (status >= Success && cnt && *list) {
+          if (cnt > 1) {
+            LOG(LS_INFO) << "Window has " << cnt
+                         << " text properties, only using the first one.";
+          }
+          *title = *list;
+          result = true;
+        }
+        if (list != NULL) {
+          XFreeStringList(list);
+        }
+      }
+      if (window_name.value != NULL) {
+        XFree(window_name.value);
+      }
+    }
+    return result;
+  }
+
+  Window GetApplicationWindow(Window window) {
+    Window root, parent;
+    Window app_window = 0;
+    Window *children;
+    unsigned int num_children;
+    Atom type = None;
+    int format;
+    unsigned long nitems, after;
+    unsigned char *data;
+
+    int ret = XGetWindowProperty(display_, window,
+                                 wm_state_, 0L, 2,
+                                 False, wm_state_, &type, &format,
+                                 &nitems, &after, &data);
+    if (ret != Success) {
+      LOG(LS_ERROR) << "XGetWindowProperty failed with return code " << ret
+                    << " for window " << window << ".";
+      return 0;
+    }
+    if (type != None) {
+      int64 state = static_cast<int64>(*data);
+      XFree(data);
+      return state == NormalState ? window : 0;
+    }
+    XFree(data);
+    if (!XQueryTree(display_, window, &root, &parent, &children,
+                    &num_children)) {
+      LOG(LS_ERROR) << "Failed to query for child windows although window"
+                    << "does not have a valid WM_STATE.";
+      return 0;
+    }
+    for (unsigned int i = 0; i < num_children; ++i) {
+      app_window = GetApplicationWindow(children[i]);
+      if (app_window) {
+        break;
+      }
+    }
+    if (children != NULL) {
+      XFree(children);
+    }
+    return app_window;
+  }
+
+  Atom wm_state_;
+  Atom net_wm_icon_;
+  Display* display_;
+  bool has_composite_extension_;
+  bool has_render_extension_;
+};
+
+LinuxWindowPicker::LinuxWindowPicker() : enumerator_(new XWindowEnumerator()) {
+}
+
+LinuxWindowPicker::~LinuxWindowPicker() {
+}
+
+bool LinuxWindowPicker::IsDesktopElement(_XDisplay* display, Window window) {
+  if (window == 0) {
+    LOG(LS_WARNING) << "Zero is never a valid window.";
+    return false;
+  }
+  XClassHint class_hint;
+  Status s = XGetClassHint(display, window, &class_hint);
+  bool result = false;
+  if (s == 0) {
+    // No hints, assume this is a normal application window.
+    return result;
+  }
+  static const std::string gnome_panel("gnome-panel");
+  static const std::string desktop_window("desktop_window");
+
+  if (gnome_panel.compare(class_hint.res_name) == 0 ||
+      desktop_window.compare(class_hint.res_name) == 0) {
+    result = true;
+  }
+  XFree(class_hint.res_name);
+  XFree(class_hint.res_class);
+  return result;
+}
+
+bool LinuxWindowPicker::Init() {
+  return enumerator_->Init();
+}
+
+bool LinuxWindowPicker::GetWindowList(WindowDescriptionList* descriptions) {
+  return enumerator_->EnumerateWindows(descriptions);
+}
+
+bool LinuxWindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) {
+  return enumerator_->EnumerateDesktops(descriptions);
+}
+
+bool LinuxWindowPicker::IsVisible(const WindowId& id) {
+  return enumerator_->IsVisible(id);
+}
+
+bool LinuxWindowPicker::MoveToFront(const WindowId& id) {
+  return enumerator_->MoveToFront(id);
+}
+
+
+uint8* LinuxWindowPicker::GetWindowIcon(const WindowId& id, int* width,
+                                        int* height) {
+  return enumerator_->GetWindowIcon(id, width, height);
+}
+
+uint8* LinuxWindowPicker::GetWindowThumbnail(const WindowId& id, int width,
+                                             int height) {
+  return enumerator_->GetWindowThumbnail(id, width, height);
+}
+
+int LinuxWindowPicker::GetNumDesktops() {
+  return enumerator_->GetNumDesktops();
+}
+
+uint8* LinuxWindowPicker::GetDesktopThumbnail(const DesktopId& id,
+                                              int width,
+                                              int height) {
+  return enumerator_->GetDesktopThumbnail(id, width, height);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/linuxwindowpicker.h b/talk/base/linuxwindowpicker.h
new file mode 100644
index 0000000..ad92c6f
--- /dev/null
+++ b/talk/base/linuxwindowpicker.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_LINUXWINDOWPICKER_H_
+#define TALK_BASE_LINUXWINDOWPICKER_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/windowpicker.h"
+
+// Avoid include <X11/Xlib.h>.
+struct _XDisplay;
+typedef unsigned long Window;
+
+namespace talk_base {
+
+class XWindowEnumerator;
+
+class LinuxWindowPicker : public WindowPicker {
+ public:
+  LinuxWindowPicker();
+  ~LinuxWindowPicker();
+
+  static bool IsDesktopElement(_XDisplay* display, Window window);
+
+  virtual bool Init();
+  virtual bool IsVisible(const WindowId& id);
+  virtual bool MoveToFront(const WindowId& id);
+  virtual bool GetWindowList(WindowDescriptionList* descriptions);
+  virtual bool GetDesktopList(DesktopDescriptionList* descriptions);
+  uint8* GetWindowIcon(const WindowId& id, int* width, int* height);
+  uint8* GetWindowThumbnail(const WindowId& id, int width, int height);
+  int GetNumDesktops();
+  uint8* GetDesktopThumbnail(const DesktopId& id, int width, int height);
+
+ private:
+  scoped_ptr<XWindowEnumerator> enumerator_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_LINUXWINDOWPICKER_H_
diff --git a/talk/base/linuxwindowpicker_unittest.cc b/talk/base/linuxwindowpicker_unittest.cc
new file mode 100644
index 0000000..fa0d49a
--- /dev/null
+++ b/talk/base/linuxwindowpicker_unittest.cc
@@ -0,0 +1,46 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/linuxwindowpicker.h"
+#include "talk/base/logging.h"
+#include "talk/base/windowpicker.h"
+
+#ifndef LINUX
+#error Only for Linux
+#endif
+
+namespace talk_base {
+
+TEST(LinuxWindowPickerTest, TestGetWindowList) {
+  LinuxWindowPicker window_picker;
+  WindowDescriptionList descriptions;
+  window_picker.Init();
+  window_picker.GetWindowList(&descriptions);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/logging.cc b/talk/base/logging.cc
index b5c588c..f094042 100644
--- a/talk/base/logging.cc
+++ b/talk/base/logging.cc
@@ -51,7 +51,7 @@
 #include "talk/base/stream.h"
 #include "talk/base/stringencode.h"
 #include "talk/base/stringutils.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 
 namespace talk_base {
 
diff --git a/talk/base/macasyncsocket.cc b/talk/base/macasyncsocket.cc
index e31be57..4924333 100644
--- a/talk/base/macasyncsocket.cc
+++ b/talk/base/macasyncsocket.cc
@@ -328,9 +328,11 @@
   }
 
   if (res) {
-    // Add this socket to the run loop
-    source_ = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
+    // Add this socket to the run loop, at priority 1 so that it will be
+    // queued behind any pending signals.
+    source_ = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 1);
     res = (source_ != NULL);
+    if (!res) errno = EINVAL;
   }
 
   if (res) {
diff --git a/talk/base/maccocoasocketserver.h b/talk/base/maccocoasocketserver.h
new file mode 100644
index 0000000..41c9f71
--- /dev/null
+++ b/talk/base/maccocoasocketserver.h
@@ -0,0 +1,106 @@
+/*
+ * libjingle
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// A libjingle compatible SocketServer for OSX/iOS/Cocoa.
+
+#if !defined(USE_COCOA_THREADING) || (!defined(OSX) && !defined(IOS))
+#error You can only include this file on OSX or IOS, and must also define
+#error USE_COCOA_THREADING.
+#endif
+
+#import <Foundation/Foundation.h>
+
+#import "talk/base/messagequeue.h"
+#import "talk/base/socketserver.h"
+
+namespace talk_base {
+class MacCocoaSocketServer;
+}
+
+// MacCocoaSocketServerHelper serves as a delegate to NSMachPort or a target for
+// a timeout.
+@interface MacCocoaSocketServerHelper : NSObject {
+  // This is a weak reference. This works fine since the
+  // talk_base::MacCocoaSocketServer owns this object.
+  talk_base::MacCocoaSocketServer* socketServer; // weak.
+}
+
+@end
+
+namespace talk_base {
+
+// The name "SocketServer" is misleading for this class. This class inherits
+// from SocketServer, some variants of which create/use physical
+// sockets (specifically, PhysicalSocketServer). A general explanation
+// (courtesy of bpm) is that SocketServer is responsible for allowing a thread
+// to go into an efficient wait state while it waits for a timer to expire or an
+// incoming message from MessageQueue.  For GUI applications, the main thread
+// must continue to pump the main event loop during this wait state, hence the
+// need for specialized SocketServers to run on the main thread.
+//
+// This particular socketServer however cannot handle a generalized wait state.
+// It can only handle Wait when called with cms == 0, or when cms > 0 and
+// process_io is false. In the former case, the process_io argument is ignored,
+// and in the latter case, the thread will sleep for cms milliSeconds.
+//
+// Use this class in a Cocoa application that uses libjingle's Task and
+// MessageQueue, on the main thread. The typical usage is something like this:
+//
+//  talk_base::Thread* current = talk_base::Thread::Current();
+//  MacCocoaSocketServer* ss =
+//      new talk_base::MacCocoaSocketServer(current);
+//  current->set_socketserver(ss);
+//
+//  // Now schedule some libjingle tasks, then call
+//
+//  [NSApp run];
+
+class MacCocoaSocketServer : public SocketServer {
+ public:
+  explicit MacCocoaSocketServer(MessageQueue* message_queue);
+  virtual ~MacCocoaSocketServer();
+
+  // SocketServer Interface. We don't create any synchronous sockets.
+  virtual Socket* CreateSocket(int type) { return NULL; }
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+
+  virtual bool Wait(int cms, bool process_io);
+  virtual void WakeUp();
+
+  void Pump();
+
+ private:
+  // message_queue_ is not owned by this object.
+  // It is cached from the constructor argument.
+  MessageQueue* message_queue_; // weak.
+  MacCocoaSocketServerHelper* helper_;
+  NSTimer* timer_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(MacCocoaSocketServer);
+};
+
+}  // namespace talk_base
diff --git a/talk/base/maccocoasocketserver.mm b/talk/base/maccocoasocketserver.mm
new file mode 100644
index 0000000..6a91d43
--- /dev/null
+++ b/talk/base/maccocoasocketserver.mm
@@ -0,0 +1,127 @@
+/*
+ * libjingle
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <assert.h>
+
+#import "talk/base/maccocoasocketserver.h"
+#import "talk/base/logging.h"
+#import "talk/base/macasyncsocket.h"
+
+static const double kTimerIntervalSecs = 0.1;
+
+@implementation MacCocoaSocketServerHelper
+
+- (id)initWithSocketServer:(talk_base::MacCocoaSocketServer*)ss {
+  self = [super init];
+  if (self) {
+    socketServer = ss;
+  }
+  return self;
+}
+
+- (void)timerFired:(NSTimer*)timer {
+  socketServer->Pump();
+}
+
+- (void)wakeUp {
+  socketServer->Pump();
+}
+
+@end
+
+namespace talk_base {
+
+MacCocoaSocketServer::MacCocoaSocketServer(MessageQueue* message_queue) :
+    message_queue_(message_queue) {
+  helper_ = [[MacCocoaSocketServerHelper alloc] initWithSocketServer:this];
+
+  NSTimer* timer =
+      [NSTimer scheduledTimerWithTimeInterval:kTimerIntervalSecs
+                                       target:helper_
+                                     selector:@selector(timerFired:)
+                                     userInfo:nil
+                                      repeats:YES];
+  timer_ = [timer retain];
+}
+
+MacCocoaSocketServer::~MacCocoaSocketServer() {
+  [timer_ invalidate];
+  [timer_ release];
+  [helper_ release];
+}
+
+AsyncSocket* MacCocoaSocketServer::CreateAsyncSocket(int type) {
+  assert(type == SOCK_STREAM);
+  return new MacAsyncSocket();
+}
+
+bool MacCocoaSocketServer::Wait(int cms, bool process_io) {
+  // Ideally we would have some way to run the UI loop for |cms| milliSeconds,
+  // or until WakeUp() is called (whichever is earlier).
+  // But there is no good solution for that - stopping/restarting the
+  // NSApp run loop or calling nextEventMatchingTask both have significant
+  // overhead, resulting in high CPU utilization when there are a lot of
+  // libjingle messages (hence WakeUp and Wait calls) floating around.
+
+  // Simply calling "usleep" will block the UI, which is OK when |process_io|
+  // is false.
+
+  if (cms != 0) {
+    assert(cms > 0 && !process_io);
+    if (cms < 0 || process_io) {
+      return false;
+    }
+    usleep(cms * 1000);
+  }
+
+  return true;
+}
+
+void MacCocoaSocketServer::WakeUp() {
+  [helper_ performSelectorOnMainThread:@selector(wakeUp)
+                            withObject:nil
+                         waitUntilDone:NO];
+}
+
+void MacCocoaSocketServer::Pump() {
+  // Process messages.
+
+  Message msg;
+  // We don't want to process an unbounded number of messages - while we do that
+  // the UI remains blocked. So we only process as many messages as are in the
+  // queue when we start.
+  //
+  // max(1,..) ensures we run Get() at least once, this is needed to check
+  // for "sent" messages that otherwise are not included into size() result.
+  for (size_t max_messages_to_process = _max<size_t>(1, message_queue_->size());
+       max_messages_to_process > 0 && message_queue_->Get(&msg, 0);
+       --max_messages_to_process) {
+    message_queue_->Dispatch(&msg);
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/maccocoathreadhelper.h b/talk/base/maccocoathreadhelper.h
new file mode 100644
index 0000000..a3cb3a8
--- /dev/null
+++ b/talk/base/maccocoathreadhelper.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Helper function for using Cocoa with Posix threads. This header should be
+// included from C/C++ files that want to use some Cocoa functionality without
+// using the .mm extension (mostly for files that are compiled on multiple
+// platforms).
+
+#ifndef TALK_BASE_MACCOCOATHREADHELPER_H__
+#define TALK_BASE_MACCOCOATHREADHELPER_H__
+
+#if !defined(USE_COCOA_THREADING) || (!defined(OSX) && !defined(IOS))
+#error You can only include this file on OSX or IOS, and must also define
+#error USE_COCOA_THREADING.
+#endif
+
+namespace talk_base {
+
+// Cocoa must be "put into multithreading mode" before Cocoa functionality can
+// be used on POSIX threads. This function does that.
+void InitCocoaMultiThreading();
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MACCOCOATHREADHELPER_H__
diff --git a/talk/base/maccocoathreadhelper.mm b/talk/base/maccocoathreadhelper.mm
new file mode 100644
index 0000000..e8482ce
--- /dev/null
+++ b/talk/base/maccocoathreadhelper.mm
@@ -0,0 +1,78 @@
+/*
+ * libjingle
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+// Helper function for using Cocoa with Posix threading.
+
+#import <assert.h>
+#import <Foundation/Foundation.h>
+
+#import "talk/base/maccocoathreadhelper.h"
+
+// A dummy thread object which essentially does nothing.
+// This is used in InitCocoaMultiThreading method.
+@interface DummyThreadObject : NSObject {
+}
+
++ (void)dummyFunc:(NSObject*)obj;
+
+@end
+
+@implementation DummyThreadObject
+
++ (void)dummyFunc:(NSObject*)obj {
+  // nothing to do
+}
+
+@end
+
+namespace talk_base {
+
+// Cocoa must be "put into multithreading mode" before Cocoa functionality can
+// be used on POSIX threads. The way to do that is to spawn one thread that may
+// immediately exit.
+void InitCocoaMultiThreading() {
+  if ([NSThread isMultiThreaded] == NO) {
+    // The sole purpose of this autorelease pool is to avoid a console
+    // message on Leopard that tells us we're autoreleasing the thread
+    // with no autorelease pool in place; we can't set up an autorelease
+    // pool before this, because this is executed from an initializer,
+    // which is run before main.  This means we leak an autorelease pool,
+    // and one thread, and if other objects are set up in initializers after
+    // this they'll be silently added to this pool and never released.
+
+    // Doing NSAutoreleasePool* hack = [[NSAutoreleasePool alloc] init];
+    // causes unused variable error.
+    NSAutoreleasePool* hack;
+    hack = [[NSAutoreleasePool alloc] init];
+    [NSThread detachNewThreadSelector:@selector(dummyFunc:)
+                             toTarget:[DummyThreadObject class]
+                           withObject:nil];
+  }
+
+  assert([NSThread isMultiThreaded]);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/macsocketserver.cc b/talk/base/macsocketserver.cc
index 154b83d..5b2ec03 100644
--- a/talk/base/macsocketserver.cc
+++ b/talk/base/macsocketserver.cc
@@ -41,6 +41,47 @@
   ASSERT(found == 1);
 }
 
+bool MacBaseSocketServer::SetPosixSignalHandler(int signum,
+                                                void (*handler)(int)) {
+  Dispatcher* dispatcher = signal_dispatcher();
+  if (!PhysicalSocketServer::SetPosixSignalHandler(signum, handler)) {
+    return false;
+  }
+
+  // Only register the FD once, when the first custom handler is installed.
+  if (!dispatcher && (dispatcher = signal_dispatcher())) {
+    CFFileDescriptorContext ctx = { 0 };
+    ctx.info = this;
+
+    CFFileDescriptorRef desc = CFFileDescriptorCreate(
+        kCFAllocatorDefault,
+        dispatcher->GetDescriptor(),
+        false,
+        &MacBaseSocketServer::FileDescriptorCallback,
+        &ctx);
+    if (!desc) {
+      return false;
+    }
+
+    CFFileDescriptorEnableCallBacks(desc, kCFFileDescriptorReadCallBack);
+    CFRunLoopSourceRef ref =
+        CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, desc, 0);
+
+    if (!ref) {
+      CFRelease(desc);
+      return false;
+    }
+
+    CFRunLoopAddSource(CFRunLoopGetCurrent(), ref, kCFRunLoopCommonModes);
+    CFRelease(desc);
+    CFRelease(ref);
+  }
+
+  return true;
+}
+
+// Used to disable socket events from waking our message queue when
+// process_io is false.  Does not disable signal event handling though.
 void MacBaseSocketServer::EnableSocketCallbacks(bool enable) {
   for (std::set<MacAsyncSocket*>::iterator it = sockets().begin();
        it != sockets().end(); ++it) {
@@ -52,6 +93,21 @@
   }
 }
 
+void MacBaseSocketServer::FileDescriptorCallback(CFFileDescriptorRef fd,
+                                                 CFOptionFlags flags,
+                                                 void* context) {
+  MacBaseSocketServer* this_ss =
+      reinterpret_cast<MacBaseSocketServer*>(context);
+  ASSERT(this_ss);
+  Dispatcher* signal_dispatcher = this_ss->signal_dispatcher();
+  ASSERT(signal_dispatcher);
+
+  signal_dispatcher->OnPreEvent(DE_READ);
+  signal_dispatcher->OnEvent(DE_READ, 0);
+  CFFileDescriptorEnableCallBacks(fd, kCFFileDescriptorReadCallBack);
+}
+
+
 ///////////////////////////////////////////////////////////////////////////////
 // MacCFSocketServer
 ///////////////////////////////////////////////////////////////////////////////
@@ -225,6 +281,9 @@
 // MacCarbonAppSocketServer
 ///////////////////////////////////////////////////////////////////////////////
 
+// Carbon is deprecated for x64.  Switch to Cocoa
+#if !defined(__x86_64__)
+
 MacCarbonAppSocketServer::MacCarbonAppSocketServer()
     : event_queue_(GetCurrentEventQueue()) {
   // Install event handler
@@ -296,6 +355,7 @@
   }
   ReleaseEvent(wake_up);
 }
+#endif
 
 ///////////////////////////////////////////////////////////////////////////////
 // MacNotificationsSocketServer
diff --git a/talk/base/macsocketserver.h b/talk/base/macsocketserver.h
index 6a4a729..53118b8 100644
--- a/talk/base/macsocketserver.h
+++ b/talk/base/macsocketserver.h
@@ -31,6 +31,9 @@
   void RegisterSocket(MacAsyncSocket* socket);
   void UnregisterSocket(MacAsyncSocket* socket);
 
+  // PhysicalSocketServer Overrides
+  virtual bool SetPosixSignalHandler(int signum, void (*handler)(int));
+
  protected:
   void EnableSocketCallbacks(bool enable);
   const std::set<MacAsyncSocket*>& sockets() {
@@ -38,6 +41,10 @@
   }
 
  private:
+  static void FileDescriptorCallback(CFFileDescriptorRef ref,
+                                     CFOptionFlags flags,
+                                     void* context);
+
   std::set<MacAsyncSocket*> sockets_;
 };
 
diff --git a/talk/base/macsocketserver_unittest.cc b/talk/base/macsocketserver_unittest.cc
new file mode 100644
index 0000000..a204c24
--- /dev/null
+++ b/talk/base/macsocketserver_unittest.cc
@@ -0,0 +1,195 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socket_unittest.h"
+#include "talk/base/thread.h"
+#include "talk/base/macsocketserver.h"
+
+namespace talk_base {
+
+class WakeThread : public Thread {
+ public:
+  WakeThread(SocketServer* ss) : ss_(ss) {
+  }
+  void Run() {
+    ss_->WakeUp();
+  }
+ private:
+  SocketServer* ss_;
+};
+
+// Test that MacCFSocketServer::Wait works as expected.
+TEST(MacCFSocketServerTest, TestWait) {
+  MacCFSocketServer server;
+  uint32 start = Time();
+  server.Wait(1000, true);
+  EXPECT_GE(TimeSince(start), 1000);
+}
+
+// Test that MacCFSocketServer::Wakeup works as expected.
+TEST(MacCFSocketServerTest, TestWakeup) {
+  MacCFSocketServer server;
+  WakeThread thread(&server);
+  uint32 start = Time();
+  thread.Start();
+  server.Wait(10000, true);
+  EXPECT_LT(TimeSince(start), 10000);
+}
+
+// Test that MacCarbonSocketServer::Wait works as expected.
+TEST(MacCarbonSocketServerTest, TestWait) {
+  MacCarbonSocketServer server;
+  uint32 start = Time();
+  server.Wait(1000, true);
+  EXPECT_GE(TimeSince(start), 1000);
+}
+
+// Test that MacCarbonSocketServer::Wakeup works as expected.
+TEST(MacCarbonSocketServerTest, TestWakeup) {
+  MacCarbonSocketServer server;
+  WakeThread thread(&server);
+  uint32 start = Time();
+  thread.Start();
+  server.Wait(10000, true);
+  EXPECT_LT(TimeSince(start), 10000);
+}
+
+// Test that MacCarbonAppSocketServer::Wait works as expected.
+TEST(MacCarbonAppSocketServerTest, TestWait) {
+  MacCarbonAppSocketServer server;
+  uint32 start = Time();
+  server.Wait(1000, true);
+  EXPECT_GE(TimeSince(start), 1000);
+}
+
+// Test that MacCarbonAppSocketServer::Wakeup works as expected.
+TEST(MacCarbonAppSocketServerTest, TestWakeup) {
+  MacCarbonAppSocketServer server;
+  WakeThread thread(&server);
+  uint32 start = Time();
+  thread.Start();
+  server.Wait(10000, true);
+  EXPECT_LT(TimeSince(start), 10000);
+}
+
+// Test that MacAsyncSocket passes all the generic Socket tests.
+class MacAsyncSocketTest : public SocketTest {
+ protected:
+  MacAsyncSocketTest()
+      : server_(CreateSocketServer()),
+        scope_(server_.get()) {}
+  // Override for other implementations of MacBaseSocketServer.
+  virtual MacBaseSocketServer* CreateSocketServer() {
+    return new MacCFSocketServer();
+  };
+  talk_base::scoped_ptr<MacBaseSocketServer> server_;
+  SocketServerScope scope_;
+};
+
+TEST_F(MacAsyncSocketTest, TestConnect) {
+  SocketTest::TestConnect();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectWithDnsLookup) {
+  SocketTest::TestConnectWithDnsLookup();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectFail) {
+  SocketTest::TestConnectFail();
+}
+
+// Reenable once we have mac async dns
+TEST_F(MacAsyncSocketTest, DISABLED_TestConnectWithDnsLookupFail) {
+  SocketTest::TestConnectWithDnsLookupFail();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectWithClosedSocket) {
+  SocketTest::TestConnectWithClosedSocket();
+}
+
+// Flaky at the moment (10% failure rate).  Seems the client doesn't get
+// signalled in a timely manner...
+TEST_F(MacAsyncSocketTest, DISABLED_TestServerCloseDuringConnect) {
+  SocketTest::TestServerCloseDuringConnect();
+}
+// Flaky at the moment (0.5% failure rate).  Seems the client doesn't get
+// signalled in a timely manner...
+TEST_F(MacAsyncSocketTest, TestClientCloseDuringConnect) {
+  SocketTest::TestClientCloseDuringConnect();
+}
+
+TEST_F(MacAsyncSocketTest, TestServerClose) {
+  SocketTest::TestServerClose();
+}
+
+TEST_F(MacAsyncSocketTest, TestCloseInClosedCallback) {
+  SocketTest::TestCloseInClosedCallback();
+}
+
+TEST_F(MacAsyncSocketTest, TestSocketServerWait) {
+  SocketTest::TestSocketServerWait();
+}
+
+TEST_F(MacAsyncSocketTest, TestTcp) {
+  SocketTest::TestTcp();
+}
+
+TEST_F(MacAsyncSocketTest, TestSingleFlowControlCallback) {
+  SocketTest::TestSingleFlowControlCallback();
+}
+
+TEST_F(MacAsyncSocketTest, DISABLED_TestUdp) {
+  SocketTest::TestUdp();
+}
+
+TEST_F(MacAsyncSocketTest, DISABLED_TestGetSetOptions) {
+  SocketTest::TestGetSetOptions();
+}
+
+class MacCarbonAsyncSocketTest : public MacAsyncSocketTest {
+  virtual MacBaseSocketServer* CreateSocketServer() {
+    return new MacCarbonSocketServer();
+  };
+};
+
+TEST_F(MacCarbonAsyncSocketTest, TestSocketServerWait) {
+  SocketTest::TestSocketServerWait();
+}
+
+class MacCarbonAppAsyncSocketTest : public MacAsyncSocketTest {
+  virtual MacBaseSocketServer* CreateSocketServer() {
+    return new MacCarbonAppSocketServer();
+  };
+};
+
+TEST_F(MacCarbonAppAsyncSocketTest, TestSocketServerWait) {
+  SocketTest::TestSocketServerWait();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/macutils_unittest.cc b/talk/base/macutils_unittest.cc
new file mode 100644
index 0000000..c66000a
--- /dev/null
+++ b/talk/base/macutils_unittest.cc
@@ -0,0 +1,54 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/macutils.h"
+
+TEST(MacUtilsTest, GetOsVersionName) {
+  talk_base::MacOSVersionName ver = talk_base::GetOSVersionName();
+  EXPECT_NE(talk_base::kMacOSUnknown, ver);
+}
+
+TEST(MacUtilsTest, GetQuickTimeVersion) {
+  std::string version;
+  EXPECT_TRUE(talk_base::GetQuickTimeVersion(&version));
+}
+
+TEST(MacUtilsTest, RunAppleScriptCompileError) {
+  std::string script("set value to to 5");
+  EXPECT_FALSE(talk_base::RunAppleScript(script));
+}
+
+TEST(MacUtilsTest, RunAppleScriptRuntimeError) {
+  std::string script("set value to 5 / 0");
+  EXPECT_FALSE(talk_base::RunAppleScript(script));
+}
+
+TEST(MacUtilsTest, RunAppleScriptSuccess) {
+  std::string script("set value to 5");
+  EXPECT_TRUE(talk_base::RunAppleScript(script));
+}
diff --git a/talk/base/macwindowpicker.cc b/talk/base/macwindowpicker.cc
new file mode 100644
index 0000000..d9cb752
--- /dev/null
+++ b/talk/base/macwindowpicker.cc
@@ -0,0 +1,241 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#include "talk/base/macwindowpicker.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <dlfcn.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/macutils.h"
+
+namespace talk_base {
+
+static const char* kCoreGraphicsName =
+    "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/"
+    "CoreGraphics.framework/CoreGraphics";
+
+static const char* kWindowListCopyWindowInfo = "CGWindowListCopyWindowInfo";
+static const char* kWindowListCreateDescriptionFromArray =
+    "CGWindowListCreateDescriptionFromArray";
+
+// Function pointer for holding the CGWindowListCopyWindowInfo function.
+typedef CFArrayRef(*CGWindowListCopyWindowInfoProc)(CGWindowListOption,
+                                                    CGWindowID);
+
+// Function pointer for holding the CGWindowListCreateDescriptionFromArray
+// function.
+typedef CFArrayRef(*CGWindowListCreateDescriptionFromArrayProc)(CFArrayRef);
+
+MacWindowPicker::MacWindowPicker() : lib_handle_(NULL), get_window_list_(NULL),
+                                     get_window_list_desc_(NULL) {
+}
+
+MacWindowPicker::~MacWindowPicker() {
+  if (lib_handle_ != NULL) {
+    dlclose(lib_handle_);
+  }
+}
+
+bool MacWindowPicker::Init() {
+  // TODO: If this class grows to use more dynamically functions
+  // from the CoreGraphics framework, consider using
+  // talk/base/latebindingsymboltable.h.
+  lib_handle_ = dlopen(kCoreGraphicsName, RTLD_NOW);
+  if (lib_handle_ == NULL) {
+    LOG(LS_ERROR) << "Could not load CoreGraphics";
+    return false;
+  }
+
+  get_window_list_ = dlsym(lib_handle_, kWindowListCopyWindowInfo);
+  get_window_list_desc_ =
+      dlsym(lib_handle_, kWindowListCreateDescriptionFromArray);
+  if (get_window_list_ == NULL || get_window_list_desc_ == NULL) {
+    // The CGWindowListCopyWindowInfo and the
+    // CGWindowListCreateDescriptionFromArray functions was introduced
+    // in Leopard(10.5) so this is a normal failure on Tiger.
+    LOG(LS_INFO) << "Failed to load Core Graphics symbols";
+    dlclose(lib_handle_);
+    lib_handle_ = NULL;
+    return false;
+  }
+
+  return true;
+}
+
+bool MacWindowPicker::IsVisible(const WindowId& id) {
+  // Init if we're not already inited.
+  if (get_window_list_desc_ == NULL && !Init()) {
+    return false;
+  }
+  CGWindowID ids[1];
+  ids[0] = id.id();
+  CFArrayRef window_id_array =
+      CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL);
+
+  CFArrayRef window_array =
+      reinterpret_cast<CGWindowListCreateDescriptionFromArrayProc>(
+          get_window_list_desc_)(window_id_array);
+  if (window_array == NULL || 0 == CFArrayGetCount(window_array)) {
+    // Could not find the window. It might have been closed.
+    LOG(LS_INFO) << "Window not found";
+    CFRelease(window_id_array);
+    return false;
+  }
+
+  CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+      CFArrayGetValueAtIndex(window_array, 0));
+  CFBooleanRef is_visible = reinterpret_cast<CFBooleanRef>(
+      CFDictionaryGetValue(window, kCGWindowIsOnscreen));
+
+  // Check that the window is visible. If not we might crash.
+  bool visible = false;
+  if (is_visible != NULL) {
+    visible = CFBooleanGetValue(is_visible);
+  }
+  CFRelease(window_id_array);
+  CFRelease(window_array);
+  return visible;
+}
+
+bool MacWindowPicker::MoveToFront(const WindowId& id) {
+  // Init if we're not already initialized.
+  if (get_window_list_desc_ == NULL && !Init()) {
+    return false;
+  }
+  CGWindowID ids[1];
+  ids[0] = id.id();
+  CFArrayRef window_id_array =
+      CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL);
+
+  CFArrayRef window_array =
+      reinterpret_cast<CGWindowListCreateDescriptionFromArrayProc>(
+          get_window_list_desc_)(window_id_array);
+  if (window_array == NULL || 0 == CFArrayGetCount(window_array)) {
+    // Could not find the window. It might have been closed.
+    LOG(LS_INFO) << "Window not found";
+    CFRelease(window_id_array);
+    return false;
+  }
+
+  CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+      CFArrayGetValueAtIndex(window_array, 0));
+  CFStringRef window_name_ref = reinterpret_cast<CFStringRef>(
+      CFDictionaryGetValue(window, kCGWindowName));
+  CFNumberRef application_pid = reinterpret_cast<CFNumberRef>(
+      CFDictionaryGetValue(window, kCGWindowOwnerPID));
+
+  int pid_val;
+  CFNumberGetValue(application_pid, kCFNumberIntType, &pid_val);
+  std::string window_name;
+  ToUtf8(window_name_ref, &window_name);
+
+  // Build an applescript that sets the selected window to front
+  // within the application. Then set the application to front.
+  bool result = true;
+  std::stringstream ss;
+  ss << "tell application \"System Events\"\n"
+     << "set proc to the first item of (every process whose unix id is "
+     << pid_val
+     << ")\n"
+     << "tell proc to perform action \"AXRaise\" of window \""
+     << window_name
+     << "\"\n"
+     << "set the frontmost of proc to true\n"
+     << "end tell";
+  if (!RunAppleScript(ss.str())) {
+    // This might happen to for example X applications where the X
+    // server spawns of processes with their own PID but the X server
+    // is still registered as owner to the application windows. As a
+    // workaround, we put the X server process to front, meaning that
+    // all X applications will show up. The drawback with this
+    // workaround is that the application that we really wanted to set
+    // to front might be behind another X application.
+    ProcessSerialNumber psn;
+    pid_t pid = pid_val;
+    int res = GetProcessForPID(pid, &psn);
+    if (res != 0) {
+      LOG(LS_ERROR) << "Failed getting process for pid";
+      result = false;
+    }
+    res = SetFrontProcess(&psn);
+    if (res != 0) {
+      LOG(LS_ERROR) << "Failed setting process to front";
+      result = false;
+    }
+  }
+  CFRelease(window_id_array);
+  CFRelease(window_array);
+  return result;
+}
+
+bool MacWindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) {
+  const uint32_t kMaxDisplays = 128;
+  CGDirectDisplayID active_displays[kMaxDisplays];
+  uint32_t display_count = 0;
+
+  CGError err = CGGetActiveDisplayList(kMaxDisplays,
+                                       active_displays,
+                                       &display_count);
+  if (err != kCGErrorSuccess) {
+    LOG_E(LS_ERROR, OS, err) << "Failed to enumerate the active displays.";
+    return false;
+  }
+  for (uint32_t i = 0; i < display_count; ++i) {
+    DesktopId id(active_displays[i], static_cast<int>(i));
+    // TODO: Figure out an appropriate desktop title.
+    DesktopDescription desc(id, "");
+    descriptions->push_back(desc);
+  }
+  return display_count > 0;
+}
+
+bool MacWindowPicker::GetWindowList(WindowDescriptionList* descriptions) {
+  // Init if we're not already inited.
+  if (get_window_list_ == NULL && !Init()) {
+    return false;
+  }
+
+  // Only get onscreen, non-desktop windows.
+  CFArrayRef window_array =
+      reinterpret_cast<CGWindowListCopyWindowInfoProc>(get_window_list_)(
+          kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
+          kCGNullWindowID);
+  if (window_array == NULL) {
+    return false;
+  }
+
+  // Check windows to make sure they have an id, title, and use window layer 0.
+  CFIndex i;
+  CFIndex count = CFArrayGetCount(window_array);
+  for (i = 0; i < count; ++i) {
+    CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+        CFArrayGetValueAtIndex(window_array, i));
+    CFStringRef window_title = reinterpret_cast<CFStringRef>(
+        CFDictionaryGetValue(window, kCGWindowName));
+    CFNumberRef window_id = reinterpret_cast<CFNumberRef>(
+        CFDictionaryGetValue(window, kCGWindowNumber));
+    CFNumberRef window_layer = reinterpret_cast<CFNumberRef>(
+        CFDictionaryGetValue(window, kCGWindowLayer));
+    if (window_title != NULL && window_id != NULL && window_layer != NULL) {
+      std::string title_str;
+      int id_val, layer_val;
+      ToUtf8(window_title, &title_str);
+      CFNumberGetValue(window_id, kCFNumberIntType, &id_val);
+      CFNumberGetValue(window_layer, kCFNumberIntType, &layer_val);
+
+      // Discard windows without a title.
+      if (layer_val == 0 && title_str.length() > 0) {
+        WindowId id(static_cast<CGWindowID>(id_val));
+        WindowDescription desc(id, title_str);
+        descriptions->push_back(desc);
+      }
+    }
+  }
+
+  CFRelease(window_array);
+  return true;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/macwindowpicker.h b/talk/base/macwindowpicker.h
new file mode 100644
index 0000000..d1ca6b8
--- /dev/null
+++ b/talk/base/macwindowpicker.h
@@ -0,0 +1,29 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#ifndef TALK_BASE_MACWINDOWPICKER_H_
+#define TALK_BASE_MACWINDOWPICKER_H_
+
+#include "talk/base/windowpicker.h"
+
+namespace talk_base {
+
+class MacWindowPicker : public WindowPicker {
+ public:
+  MacWindowPicker();
+  ~MacWindowPicker();
+  virtual bool Init();
+  virtual bool IsVisible(const WindowId& id);
+  virtual bool MoveToFront(const WindowId& id);
+  virtual bool GetWindowList(WindowDescriptionList* descriptions);
+  virtual bool GetDesktopList(DesktopDescriptionList* descriptions);
+
+ private:
+  void* lib_handle_;
+  void* get_window_list_;
+  void* get_window_list_desc_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MACWINDOWPICKER_H_
diff --git a/talk/base/macwindowpicker_unittest.cc b/talk/base/macwindowpicker_unittest.cc
new file mode 100644
index 0000000..9cb67db
--- /dev/null
+++ b/talk/base/macwindowpicker_unittest.cc
@@ -0,0 +1,39 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/macutils.h"
+#include "talk/base/macwindowpicker.h"
+#include "talk/base/windowpicker.h"
+
+#ifndef OSX
+#error Only for Mac OSX
+#endif
+
+namespace talk_base {
+
+bool IsLeopardOrLater() {
+  return GetOSVersionName() >= kMacOSLeopard;
+}
+
+// Test that this works on new versions and fails acceptably on old versions.
+TEST(MacWindowPickerTest, TestGetWindowList) {
+  MacWindowPicker picker, picker2;
+  WindowDescriptionList descriptions;
+  if (IsLeopardOrLater()) {
+    EXPECT_TRUE(picker.Init());
+    EXPECT_TRUE(picker.GetWindowList(&descriptions));
+    EXPECT_TRUE(picker2.GetWindowList(&descriptions));  // Init is optional
+  } else {
+    EXPECT_FALSE(picker.Init());
+    EXPECT_FALSE(picker.GetWindowList(&descriptions));
+    EXPECT_FALSE(picker2.GetWindowList(&descriptions));
+  }
+}
+
+// TODO: Add verification of the actual parsing, ie, add
+// functionality to inject a fake get_window_array function which
+// provide a pre-constructed list of windows.
+
+}  // namespace talk_base
diff --git a/talk/base/mathutils.h b/talk/base/mathutils.h
new file mode 100644
index 0000000..eeb110a
--- /dev/null
+++ b/talk/base/mathutils.h
@@ -0,0 +1,37 @@
+/* 
+ * libjingle
+ * Copyright 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_MATHUTILS_H_
+#define TALK_BASE_MATHUTILS_H_
+
+#include <math.h>
+
+#ifndef M_PI
+#define M_PI 3.14159265359f
+#endif
+
+#endif  // TALK_BASE_MATHUTILS_H_
diff --git a/talk/base/messagequeue.h b/talk/base/messagequeue.h
index 54d4860..2857182 100644
--- a/talk/base/messagequeue.h
+++ b/talk/base/messagequeue.h
@@ -39,9 +39,10 @@
 #include "talk/base/criticalsection.h"
 #include "talk/base/messagehandler.h"
 #include "talk/base/scoped_ptr.h"
+#include "talk/base/scoped_ref_ptr.h"
 #include "talk/base/sigslot.h"
 #include "talk/base/socketserver.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 
 namespace talk_base {
 
@@ -98,6 +99,17 @@
   scoped_ptr<T> data_;
 };
 
+// Like ScopedMessageData, but for reference counted pointers.
+template <class T>
+class ScopedRefMessageData : public MessageData {
+ public:
+  explicit ScopedRefMessageData(T* data) : data_(data) { }
+  const scoped_refptr<T>& data() const { return data_; }
+  scoped_refptr<T>& data() { return data_; }
+ private:
+  scoped_refptr<T> data_;
+};
+
 template<class T>
 inline MessageData* WrapMessageData(const T& data) {
   return new TypedMessageData<T>(data);
diff --git a/talk/base/messagequeue_unittest.cc b/talk/base/messagequeue_unittest.cc
index 7414618..1ca65ea 100644
--- a/talk/base/messagequeue_unittest.cc
+++ b/talk/base/messagequeue_unittest.cc
@@ -27,7 +27,7 @@
 
 #include "talk/base/gunit.h"
 #include "talk/base/logging.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 #include "talk/base/messagequeue.h"
 
 using namespace talk_base;
diff --git a/talk/base/multipart.cc b/talk/base/multipart.cc
new file mode 100644
index 0000000..d280ff3
--- /dev/null
+++ b/talk/base/multipart.cc
@@ -0,0 +1,268 @@
+// libjingle
+// Copyright 2004--2010, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+#include "talk/base/common.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/multipart.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// MultipartStream
+///////////////////////////////////////////////////////////////////////////////
+
+MultipartStream::MultipartStream(const std::string& type,
+                                 const std::string& boundary)
+    : type_(type),
+      boundary_(boundary),
+      adding_(true),
+      current_(0),
+      position_(0) {
+  // The content type should be multipart/*.
+  ASSERT(0 == strncmp(type_.c_str(), "multipart/", 10));
+}
+
+MultipartStream::~MultipartStream() {
+  Close();
+}
+
+void MultipartStream::GetContentType(std::string* content_type) {
+  ASSERT(NULL != content_type);
+  content_type->assign(type_);
+  content_type->append("; boundary=");
+  content_type->append(boundary_);
+}
+
+bool MultipartStream::AddPart(StreamInterface* data_stream,
+                              const std::string& content_disposition,
+                              const std::string& content_type) {
+  if (!AddPart("", content_disposition, content_type))
+    return false;
+  parts_.push_back(data_stream);
+  data_stream->SignalEvent.connect(this, &MultipartStream::OnEvent);
+  return true;
+}
+
+bool MultipartStream::AddPart(const std::string& data,
+                              const std::string& content_disposition,
+                              const std::string& content_type) {
+  ASSERT(adding_);
+  if (!adding_)
+    return false;
+  std::stringstream ss;
+  if (!parts_.empty()) {
+    ss << "\r\n";
+  }
+  ss << "--" << boundary_ << "\r\n";
+  if (!content_disposition.empty()) {
+    ss << ToString(HH_CONTENT_DISPOSITION) << ": "
+       << content_disposition << "\r\n";
+  }
+  if (!content_type.empty()) {
+    ss << ToString(HH_CONTENT_TYPE) << ": "
+       << content_type << "\r\n";
+  }
+  ss << "\r\n" << data;
+  parts_.push_back(new MemoryStream(ss.str().data(), ss.str().size()));
+  return true;
+}
+
+void MultipartStream::EndParts() {
+  ASSERT(adding_);
+  if (!adding_)
+    return;
+
+  std::stringstream ss;
+  if (!parts_.empty()) {
+    ss << "\r\n";
+  }
+  ss << "--" << boundary_ << "--" << "\r\n";
+  parts_.push_back(new MemoryStream(ss.str().data(), ss.str().size()));
+
+  ASSERT(0 == current_);
+  ASSERT(0 == position_);
+  adding_ = false;
+  SignalEvent(this, SE_OPEN | SE_READ, 0);
+}
+
+size_t MultipartStream::GetPartSize(const std::string& data,
+                                    const std::string& content_disposition,
+                                    const std::string& content_type) const {
+  size_t size = 0;
+  if (!parts_.empty()) {
+    size += 2;  // for "\r\n";
+  }
+  size += boundary_.size() + 4;  // for "--boundary_\r\n";
+  if (!content_disposition.empty()) {
+    // for ToString(HH_CONTENT_DISPOSITION): content_disposition\r\n
+    size += std::string(ToString(HH_CONTENT_DISPOSITION)).size() + 2 +
+        content_disposition.size() + 2;
+  }
+  if (!content_type.empty()) {
+    // for ToString(HH_CONTENT_TYPE): content_type\r\n
+    size += std::string(ToString(HH_CONTENT_TYPE)).size() + 2 +
+        content_type.size() + 2;
+  }
+  size += 2 + data.size();  // for \r\ndata
+  return size;
+}
+
+size_t MultipartStream::GetEndPartSize() const {
+  size_t size = 0;
+  if (!parts_.empty()) {
+    size += 2;  // for "\r\n";
+  }
+  size += boundary_.size() + 6;  // for "--boundary_--\r\n";
+  return size;
+}
+
+//
+// StreamInterface
+//
+
+StreamState MultipartStream::GetState() const {
+  if (adding_) {
+    return SS_OPENING;
+  }
+  return (current_ < parts_.size()) ? SS_OPEN : SS_CLOSED;
+}
+
+StreamResult MultipartStream::Read(void* buffer, size_t buffer_len,
+                                   size_t* read, int* error) {
+  if (adding_) {
+    return SR_BLOCK;
+  }
+  size_t local_read;
+  if (!read) read = &local_read;
+  while (current_ < parts_.size()) {
+    StreamResult result = parts_[current_]->Read(buffer, buffer_len, read,
+                                                 error);
+    if (SR_EOS != result) {
+      if (SR_SUCCESS == result) {
+        position_ += *read;
+      }
+      return result;
+    }
+    ++current_;
+  }
+  return SR_EOS;
+}
+
+StreamResult MultipartStream::Write(const void* data, size_t data_len,
+                                    size_t* written, int* error) {
+  if (error) {
+    *error = -1;
+  }
+  return SR_ERROR;
+}
+
+void MultipartStream::Close() {
+  for (size_t i = 0; i < parts_.size(); ++i) {
+    delete parts_[i];
+  }
+  parts_.clear();
+  adding_ = false;
+  current_ = 0;
+  position_ = 0;
+}
+
+bool MultipartStream::SetPosition(size_t position) {
+  if (adding_) {
+    return false;
+  }
+  size_t part_size, part_offset = 0;
+  for (size_t i = 0; i < parts_.size(); ++i) {
+    if (!parts_[i]->GetSize(&part_size)) {
+      return false;
+    }
+    if (part_offset + part_size > position) {
+      for (size_t j = i+1; j < _min(parts_.size(), current_+1); ++j) {
+        if (!parts_[j]->Rewind()) {
+          return false;
+        }
+      }
+      if (!parts_[i]->SetPosition(position - part_offset)) {
+        return false;
+      }
+      current_ = i;
+      position_ = position;
+      return true;
+    }
+    part_offset += part_size;
+  }
+  return false;
+}
+
+bool MultipartStream::GetPosition(size_t* position) const {
+  if (position) {
+    *position = position_;
+  }
+  return true;
+}
+
+bool MultipartStream::GetSize(size_t* size) const {
+  size_t part_size, total_size = 0;
+  for (size_t i = 0; i < parts_.size(); ++i) {
+    if (!parts_[i]->GetSize(&part_size)) {
+      return false;
+    }
+    total_size += part_size;
+  }
+  if (size) {
+    *size = total_size;
+  }
+  return true;
+}
+
+bool MultipartStream::GetAvailable(size_t* size) const {
+  if (adding_) {
+    return false;
+  }
+  size_t part_size, total_size = 0;
+  for (size_t i = current_; i < parts_.size(); ++i) {
+    if (!parts_[i]->GetAvailable(&part_size)) {
+      return false;
+    }
+    total_size += part_size;
+  }
+  if (size) {
+    *size = total_size;
+  }
+  return true;
+}
+
+//
+// StreamInterface Slots
+//
+
+void MultipartStream::OnEvent(StreamInterface* stream, int events, int error) {
+  if (adding_ || (current_ >= parts_.size()) || (parts_[current_] != stream)) {
+    return;
+  }
+  SignalEvent(this, events, error);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/multipart.h b/talk/base/multipart.h
new file mode 100644
index 0000000..cce592b
--- /dev/null
+++ b/talk/base/multipart.h
@@ -0,0 +1,94 @@
+// libjingle
+// Copyright 2004--2010, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_BASE_MULTIPART_H__
+#define TALK_BASE_MULTIPART_H__
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// MultipartStream - Implements an RFC2046 multipart stream by concatenating
+// the supplied parts together, and adding the correct boundaries.
+///////////////////////////////////////////////////////////////////////////////
+
+class MultipartStream : public StreamInterface, public sigslot::has_slots<> {
+ public:
+  MultipartStream(const std::string& type, const std::string& boundary);
+  virtual ~MultipartStream();
+
+  void GetContentType(std::string* content_type);
+
+  // Note: If content_disposition and/or content_type are the empty string,
+  // they will be omitted.
+  bool AddPart(StreamInterface* data_stream,
+               const std::string& content_disposition,
+               const std::string& content_type);
+  bool AddPart(const std::string& data,
+               const std::string& content_disposition,
+               const std::string& content_type);
+  void EndParts();
+
+  // Calculates the size of a part before actually adding the part.
+  size_t GetPartSize(const std::string& data,
+                     const std::string& content_disposition,
+                     const std::string& content_type) const;
+  size_t GetEndPartSize() const;
+
+  // StreamInterface
+  virtual StreamState GetState() const;
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+  virtual void Close();
+  virtual bool SetPosition(size_t position);
+  virtual bool GetPosition(size_t* position) const;
+  virtual bool GetSize(size_t* size) const;
+  virtual bool GetAvailable(size_t* size) const;
+
+ private:
+  typedef std::vector<StreamInterface*> PartList;
+
+  // StreamInterface Slots
+  void OnEvent(StreamInterface* stream, int events, int error);
+
+  std::string type_, boundary_;
+  PartList parts_;
+  bool adding_;
+  size_t current_;  // The index into parts_ of the current read position.
+  size_t position_;  // The current read position in bytes.
+
+  DISALLOW_COPY_AND_ASSIGN(MultipartStream);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MULTIPART_H__
diff --git a/talk/base/multipart_unittest.cc b/talk/base/multipart_unittest.cc
new file mode 100644
index 0000000..18e3cf9
--- /dev/null
+++ b/talk/base/multipart_unittest.cc
@@ -0,0 +1,142 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/multipart.h"
+
+namespace talk_base {
+
+static const std::string kTestMultipartBoundary = "123456789987654321";
+static const std::string kTestContentType =
+    "multipart/form-data; boundary=123456789987654321";
+static const char kTestData[] = "This is a test.";
+static const char kTestStreamContent[] = "This is a test stream.";
+
+TEST(MultipartTest, TestBasicOperations) {
+  MultipartStream multipart("multipart/form-data", kTestMultipartBoundary);
+  std::string content_type;
+  multipart.GetContentType(&content_type);
+  EXPECT_EQ(kTestContentType, content_type);
+
+  EXPECT_EQ(talk_base::SS_OPENING, multipart.GetState());
+
+  // The multipart stream contains only --boundary--\r\n
+  size_t end_part_size = multipart.GetEndPartSize();
+  multipart.EndParts();
+  EXPECT_EQ(talk_base::SS_OPEN, multipart.GetState());
+  size_t size;
+  EXPECT_TRUE(multipart.GetSize(&size));
+  EXPECT_EQ(end_part_size, size);
+
+  // Write is not supported.
+  EXPECT_EQ(talk_base::SR_ERROR,
+            multipart.Write(kTestData, sizeof(kTestData), NULL, NULL));
+
+  multipart.Close();
+  EXPECT_EQ(talk_base::SS_CLOSED, multipart.GetState());
+  EXPECT_TRUE(multipart.GetSize(&size));
+  EXPECT_EQ(0U, size);
+}
+
+TEST(MultipartTest, TestAddAndRead) {
+  MultipartStream multipart("multipart/form-data", kTestMultipartBoundary);
+
+  size_t part_size =
+      multipart.GetPartSize(kTestData, "form-data; name=\"text\"", "text");
+  EXPECT_TRUE(multipart.AddPart(kTestData, "form-data; name=\"text\"", "text"));
+  size_t size;
+  EXPECT_TRUE(multipart.GetSize(&size));
+  EXPECT_EQ(part_size, size);
+
+  talk_base::MemoryStream* stream =
+      new talk_base::MemoryStream(kTestStreamContent);
+  size_t stream_size = 0;
+  EXPECT_TRUE(stream->GetSize(&stream_size));
+  part_size +=
+      multipart.GetPartSize("", "form-data; name=\"stream\"", "stream");
+  part_size += stream_size;
+
+  EXPECT_TRUE(multipart.AddPart(
+      new talk_base::MemoryStream(kTestStreamContent),
+      "form-data; name=\"stream\"",
+      "stream"));
+  EXPECT_TRUE(multipart.GetSize(&size));
+  EXPECT_EQ(part_size, size);
+
+  // In adding state, block read.
+  char buffer[1024];
+  EXPECT_EQ(talk_base::SR_BLOCK,
+            multipart.Read(buffer, sizeof(buffer), NULL, NULL));
+  // Write is not supported.
+  EXPECT_EQ(talk_base::SR_ERROR,
+            multipart.Write(buffer, sizeof(buffer), NULL, NULL));
+
+  part_size += multipart.GetEndPartSize();
+  multipart.EndParts();
+  EXPECT_TRUE(multipart.GetSize(&size));
+  EXPECT_EQ(part_size, size);
+
+  // Read the multipart stream into StringStream
+  std::string str;
+  talk_base::StringStream str_stream(str);
+  EXPECT_EQ(talk_base::SR_SUCCESS,
+            Flow(&multipart, buffer, sizeof(buffer), &str_stream));
+  EXPECT_EQ(size, str.length());
+
+  // Search three boundaries and two parts in the order.
+  size_t pos = 0;
+  pos = str.find(kTestMultipartBoundary);
+  EXPECT_NE(std::string::npos, pos);
+  pos += kTestMultipartBoundary.length();
+
+  pos = str.find(kTestData, pos);
+  EXPECT_NE(std::string::npos, pos);
+  pos += sizeof(kTestData);
+
+  pos = str.find(kTestMultipartBoundary, pos);
+  EXPECT_NE(std::string::npos, pos);
+  pos += kTestMultipartBoundary.length();
+
+  pos = str.find(kTestStreamContent, pos);
+  EXPECT_NE(std::string::npos, pos);
+  pos += sizeof(kTestStreamContent);
+
+  pos = str.find(kTestMultipartBoundary, pos);
+  EXPECT_NE(std::string::npos, pos);
+  pos += kTestMultipartBoundary.length();
+
+  pos = str.find(kTestMultipartBoundary, pos);
+  EXPECT_EQ(std::string::npos, pos);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/nat_unittest.cc b/talk/base/nat_unittest.cc
new file mode 100644
index 0000000..8f87b3a
--- /dev/null
+++ b/talk/base/nat_unittest.cc
@@ -0,0 +1,295 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/natserver.h"
+#include "talk/base/natsocketfactory.h"
+#include "talk/base/network.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/testclient.h"
+#include "talk/base/virtualsocketserver.h"
+
+using namespace talk_base;
+
+bool CheckReceive(
+    TestClient* client, bool should_receive, const char* buf, size_t size) {
+  return (should_receive) ?
+      client->CheckNextPacket(buf, size, 0) :
+      client->CheckNoPacket();
+}
+
+TestClient* CreateTestClient(
+      SocketFactory* factory, const SocketAddress& local_addr) {
+  AsyncUDPSocket* socket = AsyncUDPSocket::Create(factory, local_addr);
+  return new TestClient(socket);
+}
+
+// Tests that when sending from internal_addr to external_addrs through the
+// NAT type specified by nat_type, all external addrs receive the sent packet
+// and, if exp_same is true, all use the same mapped-address on the NAT.
+void TestSend(
+      SocketServer* internal, const SocketAddress& internal_addr,
+      SocketServer* external, const SocketAddress external_addrs[4],
+      NATType nat_type, bool exp_same) {
+  Thread th_int(internal);
+  Thread th_ext(external);
+
+  SocketAddress server_addr = internal_addr;
+  server_addr.SetPort(0);  // Auto-select a port
+  NATServer* nat = new NATServer(
+      nat_type, internal, server_addr, external, external_addrs[0]);
+  NATSocketFactory* natsf = new NATSocketFactory(internal,
+                                                 nat->internal_address());
+
+  TestClient* in = CreateTestClient(natsf, internal_addr);
+  TestClient* out[4];
+  for (int i = 0; i < 4; i++)
+    out[i] = CreateTestClient(external, external_addrs[i]);
+
+  th_int.Start();
+  th_ext.Start();
+
+  const char* buf = "filter_test";
+  size_t len = strlen(buf);
+
+  in->SendTo(buf, len, out[0]->address());
+  SocketAddress trans_addr;
+  EXPECT_TRUE(out[0]->CheckNextPacket(buf, len, &trans_addr));
+
+  for (int i = 1; i < 4; i++) {
+    in->SendTo(buf, len, out[i]->address());
+    SocketAddress trans_addr2;
+    EXPECT_TRUE(out[i]->CheckNextPacket(buf, len, &trans_addr2));
+    bool are_same = (trans_addr == trans_addr2);
+    ASSERT_EQ(are_same, exp_same) << "same translated address";
+  }
+
+  th_int.Stop();
+  th_ext.Stop();
+
+  delete nat;
+  delete natsf;
+  delete in;
+  for (int i = 0; i < 4; i++)
+    delete out[i];
+}
+
+// Tests that when sending from external_addrs to internal_addr, the packet
+// is delivered according to the specified filter_ip and filter_port rules.
+void TestRecv(
+      SocketServer* internal, const SocketAddress& internal_addr,
+      SocketServer* external, const SocketAddress external_addrs[4],
+      NATType nat_type, bool filter_ip, bool filter_port) {
+  Thread th_int(internal);
+  Thread th_ext(external);
+
+  SocketAddress server_addr = internal_addr;
+  server_addr.SetPort(0);  // Auto-select a port
+  NATServer* nat = new NATServer(
+      nat_type, internal, server_addr, external, external_addrs[0]);
+  NATSocketFactory* natsf = new NATSocketFactory(internal,
+                                                 nat->internal_address());
+
+  TestClient* in = CreateTestClient(natsf, internal_addr);
+  TestClient* out[4];
+  for (int i = 0; i < 4; i++)
+    out[i] = CreateTestClient(external, external_addrs[i]);
+
+  th_int.Start();
+  th_ext.Start();
+
+  const char* buf = "filter_test";
+  size_t len = strlen(buf);
+
+  in->SendTo(buf, len, out[0]->address());
+  SocketAddress trans_addr;
+  EXPECT_TRUE(out[0]->CheckNextPacket(buf, len, &trans_addr));
+
+  out[1]->SendTo(buf, len, trans_addr);
+  EXPECT_TRUE(CheckReceive(in, !filter_ip, buf, len));
+
+  out[2]->SendTo(buf, len, trans_addr);
+  EXPECT_TRUE(CheckReceive(in, !filter_port, buf, len));
+
+  out[3]->SendTo(buf, len, trans_addr);
+  EXPECT_TRUE(CheckReceive(in, !filter_ip && !filter_port, buf, len));
+
+  th_int.Stop();
+  th_ext.Stop();
+
+  delete nat;
+  delete natsf;
+  delete in;
+  for (int i = 0; i < 4; i++)
+    delete out[i];
+}
+
+// Tests that NATServer allocates bindings properly.
+void TestBindings(
+    SocketServer* internal, const SocketAddress& internal_addr,
+    SocketServer* external, const SocketAddress external_addrs[4]) {
+  TestSend(internal, internal_addr, external, external_addrs,
+           NAT_OPEN_CONE, true);
+  TestSend(internal, internal_addr, external, external_addrs,
+           NAT_ADDR_RESTRICTED, true);
+  TestSend(internal, internal_addr, external, external_addrs,
+           NAT_PORT_RESTRICTED, true);
+  TestSend(internal, internal_addr, external, external_addrs,
+           NAT_SYMMETRIC, false);
+}
+
+// Tests that NATServer filters packets properly.
+void TestFilters(
+    SocketServer* internal, const SocketAddress& internal_addr,
+    SocketServer* external, const SocketAddress external_addrs[4]) {
+  TestRecv(internal, internal_addr, external, external_addrs,
+           NAT_OPEN_CONE, false, false);
+  TestRecv(internal, internal_addr, external, external_addrs,
+           NAT_ADDR_RESTRICTED, true, false);
+  TestRecv(internal, internal_addr, external, external_addrs,
+           NAT_PORT_RESTRICTED, true, true);
+  TestRecv(internal, internal_addr, external, external_addrs,
+           NAT_SYMMETRIC, true, true);
+}
+
+TEST(NatTest, TestPhysical) {
+  BasicNetworkManager network_manager;
+  network_manager.StartUpdating();
+  // Process pending messages so the network list is updated.
+  Thread::Current()->ProcessMessages(0);
+
+  std::vector<Network*> networks;
+  network_manager.GetNetworks(&networks);
+  if (networks.empty()) {
+    LOG(LS_WARNING) << "Not enough network adapters for test.";
+    return;
+  }
+
+  SocketAddress int_addr("127.0.0.1", 0);
+  std::string ext_ip1 = "127.0.0.1";
+  std::string ext_ip2 = networks[0]->ip().ToString();
+
+  LOG(LS_INFO) << "selected ip " << ext_ip2;
+
+  SocketAddress ext_addrs[4] = {
+      SocketAddress(ext_ip1, 0),
+      SocketAddress(ext_ip2, 0),
+      SocketAddress(ext_ip1, 0),
+      SocketAddress(ext_ip2, 0)
+  };
+
+  PhysicalSocketServer* int_pss = new PhysicalSocketServer();
+  PhysicalSocketServer* ext_pss = new PhysicalSocketServer();
+
+  TestBindings(int_pss, int_addr, ext_pss, ext_addrs);
+  TestFilters(int_pss, int_addr, ext_pss, ext_addrs);
+}
+
+class TestVirtualSocketServer : public VirtualSocketServer {
+ public:
+  explicit TestVirtualSocketServer(SocketServer* ss)
+      : VirtualSocketServer(ss) {}
+  // Expose this publicly
+  IPAddress GetNextIP(int af) { return VirtualSocketServer::GetNextIP(af); }
+};
+
+TEST(NatTest, TestVirtual) {
+  TestVirtualSocketServer* int_vss = new TestVirtualSocketServer(
+      new PhysicalSocketServer());
+  TestVirtualSocketServer* ext_vss = new TestVirtualSocketServer(
+      new PhysicalSocketServer());
+
+  // TODO: IPv6ize this test when the NAT stuff is v6ed.
+  SocketAddress int_addr, ext_addrs[4];
+  int_addr.SetIP(int_vss->GetNextIP(int_addr.ipaddr().family()));
+  ext_addrs[0].SetIP(ext_vss->GetNextIP(int_addr.ipaddr().family()));
+  ext_addrs[1].SetIP(ext_vss->GetNextIP(int_addr.ipaddr().family()));
+  ext_addrs[2].SetIP(ext_addrs[0].ipaddr());
+  ext_addrs[3].SetIP(ext_addrs[1].ipaddr());
+
+  TestBindings(int_vss, int_addr, ext_vss, ext_addrs);
+  TestFilters(int_vss, int_addr, ext_vss, ext_addrs);
+}
+
+
+// TODO: Finish this test
+class NatTcpTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+  NatTcpTest() : connected_(false) {}
+  virtual void SetUp() {
+    int_vss_ = new TestVirtualSocketServer(new PhysicalSocketServer());
+    ext_vss_ = new TestVirtualSocketServer(new PhysicalSocketServer());
+    nat_ = new NATServer(NAT_OPEN_CONE, int_vss_, SocketAddress(),
+                         ext_vss_, SocketAddress());
+    natsf_ = new NATSocketFactory(int_vss_, nat_->internal_address());
+  }
+  void OnConnectEvent(AsyncSocket* socket) {
+    connected_ = true;
+  }
+  void OnAcceptEvent(AsyncSocket* socket) {
+    accepted_ = server_->Accept(NULL);
+  }
+  void OnCloseEvent(AsyncSocket* socket, int error) {
+  }
+  void ConnectEvents() {
+    server_->SignalReadEvent.connect(this, &NatTcpTest::OnAcceptEvent);
+    client_->SignalConnectEvent.connect(this, &NatTcpTest::OnConnectEvent);
+  }
+  TestVirtualSocketServer* int_vss_;
+  TestVirtualSocketServer* ext_vss_;
+  NATServer* nat_;
+  NATSocketFactory* natsf_;
+  AsyncSocket* client_;
+  AsyncSocket* server_;
+  AsyncSocket* accepted_;
+  bool connected_;
+};
+
+TEST_F(NatTcpTest, DISABLED_TestConnectOut) {
+  server_ = ext_vss_->CreateAsyncSocket(SOCK_STREAM);
+  server_->Bind(SocketAddress());
+  server_->Listen(5);
+
+  client_ = int_vss_->CreateAsyncSocket(SOCK_STREAM);
+  EXPECT_GE(0, client_->Bind(SocketAddress()));
+  EXPECT_GE(0, client_->Connect(server_->GetLocalAddress()));
+
+
+  ConnectEvents();
+
+  EXPECT_TRUE_WAIT(connected_, 1000);
+  EXPECT_EQ(client_->GetRemoteAddress(), server_->GetLocalAddress());
+  EXPECT_EQ(client_->GetRemoteAddress(), accepted_->GetLocalAddress());
+  EXPECT_EQ(client_->GetLocalAddress(), accepted_->GetRemoteAddress());
+
+  client_->Close();
+}
+//#endif
diff --git a/talk/base/natserver.cc b/talk/base/natserver.cc
new file mode 100644
index 0000000..d287059
--- /dev/null
+++ b/talk/base/natserver.cc
@@ -0,0 +1,190 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/natsocketfactory.h"
+#include "talk/base/natserver.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+RouteCmp::RouteCmp(NAT* nat) : symmetric(nat->IsSymmetric()) {
+}
+
+size_t RouteCmp::operator()(const SocketAddressPair& r) const {
+  size_t h = r.source().Hash();
+  if (symmetric)
+    h ^= r.destination().Hash();
+  return h;
+}
+
+bool RouteCmp::operator()(
+      const SocketAddressPair& r1, const SocketAddressPair& r2) const {
+  if (r1.source() < r2.source())
+    return true;
+  if (r2.source() < r1.source())
+    return false;
+  if (symmetric && (r1.destination() < r2.destination()))
+    return true;
+  if (symmetric && (r2.destination() < r1.destination()))
+    return false;
+  return false;
+}
+
+AddrCmp::AddrCmp(NAT* nat)
+    : use_ip(nat->FiltersIP()), use_port(nat->FiltersPort()) {
+}
+
+size_t AddrCmp::operator()(const SocketAddress& a) const {
+  size_t h = 0;
+  if (use_ip)
+    h ^= HashIP(a.ipaddr());
+  if (use_port)
+    h ^= a.port() | (a.port() << 16);
+  return h;
+}
+
+bool AddrCmp::operator()(
+      const SocketAddress& a1, const SocketAddress& a2) const {
+  if (use_ip && (a1.ipaddr() < a2.ipaddr()))
+    return true;
+  if (use_ip && (a2.ipaddr() < a1.ipaddr()))
+    return false;
+  if (use_port && (a1.port() < a2.port()))
+    return true;
+  if (use_port && (a2.port() < a1.port()))
+    return false;
+  return false;
+}
+
+NATServer::NATServer(
+    NATType type, SocketFactory* internal, const SocketAddress& internal_addr,
+    SocketFactory* external, const SocketAddress& external_ip)
+    : external_(external), external_ip_(external_ip.ipaddr(), 0) {
+  nat_ = NAT::Create(type);
+
+  server_socket_ = AsyncUDPSocket::Create(internal, internal_addr);
+  server_socket_->SignalReadPacket.connect(this, &NATServer::OnInternalPacket);
+
+  int_map_ = new InternalMap(RouteCmp(nat_));
+  ext_map_ = new ExternalMap();
+}
+
+NATServer::~NATServer() {
+  for (InternalMap::iterator iter = int_map_->begin();
+       iter != int_map_->end();
+       iter++)
+    delete iter->second;
+
+  delete nat_;
+  delete server_socket_;
+  delete int_map_;
+  delete ext_map_;
+}
+
+void NATServer::OnInternalPacket(
+    AsyncPacketSocket* socket, const char* buf, size_t size,
+    const SocketAddress& addr) {
+
+  // Read the intended destination from the wire.
+  SocketAddress dest_addr;
+  int length = UnpackAddressFromNAT(buf, size, &dest_addr);
+
+  // Find the translation for these addresses (allocating one if necessary).
+  SocketAddressPair route(addr, dest_addr);
+  InternalMap::iterator iter = int_map_->find(route);
+  if (iter == int_map_->end()) {
+    Translate(route);
+    iter = int_map_->find(route);
+  }
+  ASSERT(iter != int_map_->end());
+
+  // Allow the destination to send packets back to the source.
+  iter->second->whitelist->insert(dest_addr);
+
+  // Send the packet to its intended destination.
+  iter->second->socket->SendTo(buf + length, size - length, dest_addr);
+}
+
+void NATServer::OnExternalPacket(
+    AsyncPacketSocket* socket, const char* buf, size_t size,
+    const SocketAddress& remote_addr) {
+
+  SocketAddress local_addr = socket->GetLocalAddress();
+
+  // Find the translation for this addresses.
+  ExternalMap::iterator iter = ext_map_->find(local_addr);
+  ASSERT(iter != ext_map_->end());
+
+  // Allow the NAT to reject this packet.
+  if (Filter(iter->second, remote_addr)) {
+    LOG(LS_INFO) << "Packet from " << remote_addr.ToString()
+                 << " was filtered out by the NAT.";
+    return;
+  }
+
+  // Forward this packet to the internal address.
+  // First prepend the address in a quasi-STUN format.
+  scoped_array<char> real_buf(new char[size + kNATEncodedIPv6AddressSize]);
+  size_t addrlength = PackAddressForNAT(real_buf.get(),
+                                        size + kNATEncodedIPv6AddressSize,
+                                        remote_addr);
+  // Copy the data part after the address.
+  std::memcpy(real_buf.get() + addrlength, buf, size);
+  server_socket_->SendTo(real_buf.get(), size + addrlength,
+                         iter->second->route.source());
+}
+
+void NATServer::Translate(const SocketAddressPair& route) {
+  AsyncUDPSocket* socket = AsyncUDPSocket::Create(external_, external_ip_);
+
+  if (!socket) {
+    LOG(LS_ERROR) << "Couldn't find a free port!";
+    return;
+  }
+
+  TransEntry* entry = new TransEntry(route, socket, nat_);
+  (*int_map_)[route] = entry;
+  (*ext_map_)[socket->GetLocalAddress()] = entry;
+  socket->SignalReadPacket.connect(this, &NATServer::OnExternalPacket);
+}
+
+bool NATServer::Filter(TransEntry* entry, const SocketAddress& ext_addr) {
+  return entry->whitelist->find(ext_addr) == entry->whitelist->end();
+}
+
+NATServer::TransEntry::TransEntry(
+    const SocketAddressPair& r, AsyncUDPSocket* s, NAT* nat)
+    : route(r), socket(s) {
+  whitelist = new AddressSet(AddrCmp(nat));
+}
+
+NATServer::TransEntry::~TransEntry() {
+  delete whitelist;
+  delete socket;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/natserver.h b/talk/base/natserver.h
new file mode 100644
index 0000000..0a6083c
--- /dev/null
+++ b/talk/base/natserver.h
@@ -0,0 +1,121 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_NATSERVER_H_
+#define TALK_BASE_NATSERVER_H_
+
+#include <map>
+#include <set>
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/socketaddresspair.h"
+#include "talk/base/thread.h"
+#include "talk/base/socketfactory.h"
+#include "talk/base/nattypes.h"
+
+namespace talk_base {
+
+// Change how routes (socketaddress pairs) are compared based on the type of
+// NAT.  The NAT server maintains a hashtable of the routes that it knows
+// about.  So these affect which routes are treated the same.
+struct RouteCmp {
+  explicit RouteCmp(NAT* nat);
+  size_t operator()(const SocketAddressPair& r) const;
+  bool operator()(
+      const SocketAddressPair& r1, const SocketAddressPair& r2) const;
+
+  bool symmetric;
+};
+
+// Changes how addresses are compared based on the filtering rules of the NAT.
+struct AddrCmp {
+  explicit AddrCmp(NAT* nat);
+  size_t operator()(const SocketAddress& r) const;
+  bool operator()(const SocketAddress& r1, const SocketAddress& r2) const;
+
+  bool use_ip;
+  bool use_port;
+};
+
+// Implements the NAT device.  It listens for packets on the internal network,
+// translates them, and sends them out over the external network.
+
+const int NAT_SERVER_PORT = 4237;
+
+class NATServer : public sigslot::has_slots<> {
+ public:
+  NATServer(
+      NATType type, SocketFactory* internal, const SocketAddress& internal_addr,
+      SocketFactory* external, const SocketAddress& external_ip);
+  ~NATServer();
+
+  SocketAddress internal_address() const {
+    return server_socket_->GetLocalAddress();
+  }
+
+  // Packets received on one of the networks.
+  void OnInternalPacket(AsyncPacketSocket* socket, const char* buf,
+                        size_t size, const SocketAddress& addr);
+  void OnExternalPacket(AsyncPacketSocket* socket, const char* buf,
+                        size_t size, const SocketAddress& remote_addr);
+
+ private:
+  typedef std::set<SocketAddress, AddrCmp> AddressSet;
+
+  /* Records a translation and the associated external socket. */
+  struct TransEntry {
+    TransEntry(const SocketAddressPair& r, AsyncUDPSocket* s, NAT* nat);
+    ~TransEntry();
+
+    SocketAddressPair route;
+    AsyncUDPSocket* socket;
+    AddressSet* whitelist;
+  };
+
+  typedef std::map<SocketAddressPair, TransEntry*, RouteCmp> InternalMap;
+  typedef std::map<SocketAddress, TransEntry*> ExternalMap;
+
+  /* Creates a new entry that translates the given route. */
+  void Translate(const SocketAddressPair& route);
+
+  /* Determines whether the NAT would filter out a packet from this address. */
+  bool Filter(TransEntry* entry, const SocketAddress& ext_addr);
+
+  NAT* nat_;
+  SocketFactory* internal_;
+  SocketFactory* external_;
+  SocketAddress external_ip_;
+  AsyncUDPSocket* server_socket_;
+  AsyncSocket* tcp_server_socket_;
+  InternalMap* int_map_;
+  ExternalMap* ext_map_;
+  DISALLOW_EVIL_CONSTRUCTORS(NATServer);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_NATSERVER_H_
diff --git a/talk/base/natserver_main.cc b/talk/base/natserver_main.cc
new file mode 100644
index 0000000..a748108
--- /dev/null
+++ b/talk/base/natserver_main.cc
@@ -0,0 +1,57 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+
+#include "talk/base/natserver.h"
+#include "talk/base/host.h"
+#include "talk/base/physicalsocketserver.h"
+
+using namespace talk_base;
+
+int main(int argc, char* argv[]) {
+  if (argc != 3) {
+    std::cerr << "usage: natserver <internal-ip> <external-ip>" << std::endl;
+    exit(1);
+  }
+
+  SocketAddress internal = SocketAddress(argv[1]);
+  SocketAddress external = SocketAddress(argv[2]);
+  if (internal.EqualIPs(external)) {
+    std::cerr << "internal and external IPs must differ" << std::endl;
+    exit(1);
+  }
+
+  Thread* pthMain = Thread::Current();
+  PhysicalSocketServer* ss = new PhysicalSocketServer();
+  pthMain->set_socketserver(ss);
+  NATServer* server = new NATServer(NAT_OPEN_CONE, ss, internal, ss, external);
+  server = server;
+
+  pthMain->Run();
+  return 0;
+}
diff --git a/talk/base/natsocketfactory.cc b/talk/base/natsocketfactory.cc
new file mode 100644
index 0000000..de2cf2b
--- /dev/null
+++ b/talk/base/natsocketfactory.cc
@@ -0,0 +1,488 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/natsocketfactory.h"
+
+#include "talk/base/logging.h"
+#include "talk/base/natserver.h"
+#include "talk/base/virtualsocketserver.h"
+
+namespace talk_base {
+
+// Packs the given socketaddress into the buffer in buf, in the quasi-STUN
+// format that the natserver uses.
+// Returns 0 if an invalid address is passed.
+size_t PackAddressForNAT(char* buf, size_t buf_size,
+                         const SocketAddress& remote_addr) {
+  const IPAddress& ip = remote_addr.ipaddr();
+  int family = ip.family();
+  buf[0] = 0;
+  buf[1] = family;
+  // Writes the port.
+  *(reinterpret_cast<uint16*>(&buf[2])) = htons(remote_addr.port());
+  if (family == AF_INET) {
+    ASSERT(buf_size >= kNATEncodedIPv4AddressSize);
+    in_addr v4addr = ip.ipv4_address();
+    std::memcpy(&buf[4], &v4addr, kNATEncodedIPv4AddressSize - 4);
+    return kNATEncodedIPv4AddressSize;
+  } else if (family == AF_INET6) {
+    ASSERT(buf_size >= kNATEncodedIPv6AddressSize);
+    in6_addr v6addr = ip.ipv6_address();
+    std::memcpy(&buf[4], &v6addr, kNATEncodedIPv6AddressSize - 4);
+    return kNATEncodedIPv6AddressSize;
+  }
+  return 0U;
+}
+
+// Decodes the remote address from a packet that has been encoded with the nat's
+// quasi-STUN format. Returns the length of the address (i.e., the offset into
+// data where the original packet starts).
+size_t UnpackAddressFromNAT(const char* buf, size_t buf_size,
+                            SocketAddress* remote_addr) {
+  ASSERT(buf_size >= 8);
+  ASSERT(buf[0] == 0);
+  int family = buf[1];
+  uint16 port = ntohs(*(reinterpret_cast<const uint16*>(&buf[2])));
+  if (family == AF_INET) {
+    const in_addr* v4addr = reinterpret_cast<const in_addr*>(&buf[4]);
+    *remote_addr = SocketAddress(IPAddress(*v4addr), port);
+    return kNATEncodedIPv4AddressSize;
+  } else if (family == AF_INET6) {
+    ASSERT(buf_size >= 20);
+    const in6_addr* v6addr = reinterpret_cast<const in6_addr*>(&buf[4]);
+    *remote_addr = SocketAddress(IPAddress(*v6addr), port);
+    return kNATEncodedIPv6AddressSize;
+  }
+  return 0U;
+}
+
+
+// NATSocket
+class NATSocket : public AsyncSocket, public sigslot::has_slots<> {
+ public:
+  explicit NATSocket(NATInternalSocketFactory* sf, int type)
+      : sf_(sf), type_(type), async_(true), connected_(false),
+        socket_(NULL), buf_(NULL), size_(0) {
+  }
+
+  virtual ~NATSocket() {
+    delete socket_;
+    delete[] buf_;
+  }
+
+  virtual SocketAddress GetLocalAddress() const {
+    return (socket_) ? socket_->GetLocalAddress() : SocketAddress();
+  }
+
+  virtual SocketAddress GetRemoteAddress() const {
+    return remote_addr_;  // will be ANY if not connected
+  }
+
+  virtual int Bind(const SocketAddress& addr) {
+    if (socket_) {  // already bound, bubble up error
+      return -1;
+    }
+
+    int result;
+    socket_ = sf_->CreateInternalSocket(type_, addr, &server_addr_);
+    result = (socket_) ? socket_->Bind(addr) : -1;
+    if (result >= 0) {
+      socket_->SignalConnectEvent.connect(this, &NATSocket::OnConnectEvent);
+      socket_->SignalReadEvent.connect(this, &NATSocket::OnReadEvent);
+      socket_->SignalWriteEvent.connect(this, &NATSocket::OnWriteEvent);
+      socket_->SignalCloseEvent.connect(this, &NATSocket::OnCloseEvent);
+    } else {
+      server_addr_.Clear();
+      delete socket_;
+      socket_ = NULL;
+    }
+
+    return result;
+  }
+
+  virtual int Connect(const SocketAddress& addr) {
+    if (!socket_) {  // socket must be bound, for now
+      return -1;
+    }
+
+    int result = 0;
+    if (type_ == SOCK_STREAM) {
+      result = socket_->Connect(server_addr_.IsAny() ? addr : server_addr_);
+    } else {
+      connected_ = true;
+    }
+
+    if (result >= 0) {
+      remote_addr_ = addr;
+    }
+
+    return result;
+  }
+
+  virtual int Send(const void* data, size_t size) {
+    ASSERT(connected_);
+    return SendTo(data, size, remote_addr_);
+  }
+
+  virtual int SendTo(const void* data, size_t size, const SocketAddress& addr) {
+    ASSERT(!connected_ || addr == remote_addr_);
+    if (server_addr_.IsAny() || type_ == SOCK_STREAM) {
+      return socket_->SendTo(data, size, addr);
+    }
+    // This array will be too large for IPv4 packets, but only by 12 bytes.
+    scoped_array<char> buf(new char[size + kNATEncodedIPv6AddressSize]);
+    size_t addrlength = PackAddressForNAT(buf.get(),
+                                          size + kNATEncodedIPv6AddressSize,
+                                          addr);
+    size_t encoded_size = size + addrlength;
+    std::memcpy(buf.get() + addrlength, data, size);
+    int result = socket_->SendTo(buf.get(), encoded_size, server_addr_);
+    if (result >= 0) {
+      ASSERT(result == static_cast<int>(encoded_size));
+      result = result - static_cast<int>(addrlength);
+    }
+    return result;
+  }
+
+  virtual int Recv(void* data, size_t size) {
+    SocketAddress addr;
+    return RecvFrom(data, size, &addr);
+  }
+
+  virtual int RecvFrom(void* data, size_t size, SocketAddress *out_addr) {
+    if (server_addr_.IsAny() || type_ == SOCK_STREAM) {
+      return socket_->RecvFrom(data, size, out_addr);
+    }
+    // Make sure we have enough room to read the requested amount plus the
+    // largest possible header address.
+    SocketAddress remote_addr;
+    Grow(size + kNATEncodedIPv6AddressSize);
+
+    // Read the packet from the socket.
+    int result = socket_->RecvFrom(buf_, size_, &remote_addr);
+    if (result >= 0) {
+      ASSERT(remote_addr == server_addr_);
+
+      // TODO: we need better framing so we know how many bytes we can
+      // return before we need to read the next address. For UDP, this will be
+      // fine as long as the reader always reads everything in the packet.
+      ASSERT((size_t)result < size_);
+
+      // Decode the wire packet into the actual results.
+      SocketAddress real_remote_addr;
+      size_t addrlength =
+          UnpackAddressFromNAT(buf_, result, &real_remote_addr);
+      std::memcpy(data, buf_ + addrlength, result - addrlength);
+
+      // Make sure this packet should be delivered before returning it.
+      if (!connected_ || (real_remote_addr == remote_addr_)) {
+        if (out_addr)
+          *out_addr = real_remote_addr;
+        result = result - addrlength;
+      } else {
+        LOG(LS_ERROR) << "Dropping packet from unknown remote address: "
+                      << real_remote_addr.ToString();
+        result = 0;  // Tell the caller we didn't read anything
+      }
+    }
+
+    return result;
+  }
+
+  virtual int Close() {
+    int result = 0;
+    if (socket_) {
+      result = socket_->Close();
+      if (result >= 0) {
+        connected_ = false;
+        remote_addr_ = SocketAddress();
+        delete socket_;
+        socket_ = NULL;
+      }
+    }
+    return result;
+  }
+
+  virtual int Listen(int backlog) {
+    return socket_->Listen(backlog);
+  }
+  virtual AsyncSocket* Accept(SocketAddress *paddr) {
+    return socket_->Accept(paddr);
+  }
+  virtual int GetError() const {
+    return socket_->GetError();
+  }
+  virtual void SetError(int error) {
+    socket_->SetError(error);
+  }
+  virtual ConnState GetState() const {
+    return connected_ ? CS_CONNECTED : CS_CLOSED;
+  }
+  virtual int EstimateMTU(uint16* mtu) {
+    return socket_->EstimateMTU(mtu);
+  }
+  virtual int GetOption(Option opt, int* value) {
+    return socket_->GetOption(opt, value);
+  }
+  virtual int SetOption(Option opt, int value) {
+    return socket_->SetOption(opt, value);
+  }
+
+  void OnConnectEvent(AsyncSocket* socket) {
+    // If we're NATed, we need to send a request with the real addr to use.
+    ASSERT(socket == socket_);
+    if (server_addr_.IsAny()) {
+      connected_ = true;
+      SignalConnectEvent(this);
+    } else {
+      SendConnectRequest();
+    }
+  }
+  void OnReadEvent(AsyncSocket* socket) {
+    // If we're NATed, we need to process the connect reply.
+    ASSERT(socket == socket_);
+    if (type_ == SOCK_STREAM && !server_addr_.IsAny() && !connected_) {
+      HandleConnectReply();
+    } else {
+      SignalReadEvent(this);
+    }
+  }
+  void OnWriteEvent(AsyncSocket* socket) {
+    ASSERT(socket == socket_);
+    SignalWriteEvent(this);
+  }
+  void OnCloseEvent(AsyncSocket* socket, int error) {
+    ASSERT(socket == socket_);
+    SignalCloseEvent(this, error);
+  }
+
+ private:
+  // Makes sure the buffer is at least the given size.
+  void Grow(size_t new_size) {
+    if (size_ < new_size) {
+      delete[] buf_;
+      size_ = new_size;
+      buf_ = new char[size_];
+    }
+  }
+
+  // Sends the destination address to the server to tell it to connect.
+  void SendConnectRequest() {
+    char buf[256];
+    size_t length = PackAddressForNAT(buf, ARRAY_SIZE(buf), remote_addr_);
+    socket_->Send(buf, length);
+  }
+
+  // Handles the byte sent back from the server and fires the appropriate event.
+  void HandleConnectReply() {
+    char code;
+    socket_->Recv(&code, sizeof(code));
+    if (code == 0) {
+      SignalConnectEvent(this);
+    } else {
+      Close();
+      SignalCloseEvent(this, code);
+    }
+  }
+
+  NATInternalSocketFactory* sf_;
+  int type_;
+  bool async_;
+  bool connected_;
+  SocketAddress remote_addr_;
+  SocketAddress server_addr_;  // address of the NAT server
+  AsyncSocket* socket_;
+  char* buf_;
+  size_t size_;
+};
+
+// NATSocketFactory
+NATSocketFactory::NATSocketFactory(SocketFactory* factory,
+                                   const SocketAddress& nat_addr)
+    : factory_(factory), nat_addr_(nat_addr) {
+}
+
+Socket* NATSocketFactory::CreateSocket(int type) {
+  return new NATSocket(this, type);
+}
+
+AsyncSocket* NATSocketFactory::CreateAsyncSocket(int type) {
+  return new NATSocket(this, type);
+}
+
+AsyncSocket* NATSocketFactory::CreateInternalSocket(int type,
+    const SocketAddress& local_addr, SocketAddress* nat_addr) {
+  *nat_addr = nat_addr_;
+  return factory_->CreateAsyncSocket(type);
+}
+
+// NATSocketServer
+NATSocketServer::NATSocketServer(SocketServer* server)
+    : server_(server), msg_queue_(NULL) {
+}
+
+NATSocketServer::Translator* NATSocketServer::GetTranslator(
+    const SocketAddress& ext_ip) {
+  return nats_.Get(ext_ip);
+}
+
+NATSocketServer::Translator* NATSocketServer::AddTranslator(
+    const SocketAddress& ext_ip, const SocketAddress& int_ip, NATType type) {
+  // Fail if a translator already exists with this extternal address.
+  if (nats_.Get(ext_ip))
+    return NULL;
+
+  return nats_.Add(ext_ip, new Translator(this, type, int_ip, server_, ext_ip));
+}
+
+void NATSocketServer::RemoveTranslator(
+    const SocketAddress& ext_ip) {
+  nats_.Remove(ext_ip);
+}
+
+Socket* NATSocketServer::CreateSocket(int type) {
+  return new NATSocket(this, type);
+}
+
+AsyncSocket* NATSocketServer::CreateAsyncSocket(int type) {
+  return new NATSocket(this, type);
+}
+
+AsyncSocket* NATSocketServer::CreateInternalSocket(int type,
+    const SocketAddress& local_addr, SocketAddress* nat_addr) {
+  AsyncSocket* socket = NULL;
+  Translator* nat = nats_.FindClient(local_addr);
+  if (nat) {
+    socket = nat->internal_factory()->CreateAsyncSocket(type);
+    *nat_addr = (type == SOCK_STREAM) ?
+        nat->internal_tcp_address() : nat->internal_address();
+  } else {
+    socket = server_->CreateAsyncSocket(type);
+  }
+  return socket;
+}
+
+// NATSocketServer::Translator
+NATSocketServer::Translator::Translator(
+    NATSocketServer* server, NATType type, const SocketAddress& int_ip,
+    SocketFactory* ext_factory, const SocketAddress& ext_ip)
+    : server_(server) {
+  // Create a new private network, and a NATServer running on the private
+  // network that bridges to the external network. Also tell the private
+  // network to use the same message queue as us.
+  VirtualSocketServer* internal_server = new VirtualSocketServer(server_);
+  internal_server->SetMessageQueue(server_->queue());
+  internal_factory_.reset(internal_server);
+  nat_server_.reset(new NATServer(type, internal_server, int_ip,
+                                  ext_factory, ext_ip));
+}
+
+
+NATSocketServer::Translator* NATSocketServer::Translator::GetTranslator(
+    const SocketAddress& ext_ip) {
+  return nats_.Get(ext_ip);
+}
+
+NATSocketServer::Translator* NATSocketServer::Translator::AddTranslator(
+    const SocketAddress& ext_ip, const SocketAddress& int_ip, NATType type) {
+  // Fail if a translator already exists with this extternal address.
+  if (nats_.Get(ext_ip))
+    return NULL;
+
+  AddClient(ext_ip);
+  return nats_.Add(ext_ip,
+                   new Translator(server_, type, int_ip, server_, ext_ip));
+}
+void NATSocketServer::Translator::RemoveTranslator(
+    const SocketAddress& ext_ip) {
+  nats_.Remove(ext_ip);
+  RemoveClient(ext_ip);
+}
+
+bool NATSocketServer::Translator::AddClient(
+    const SocketAddress& int_ip) {
+  // Fail if a client already exists with this internal address.
+  if (clients_.find(int_ip) != clients_.end())
+    return false;
+
+  clients_.insert(int_ip);
+  return true;
+}
+
+void NATSocketServer::Translator::RemoveClient(
+    const SocketAddress& int_ip) {
+  std::set<SocketAddress>::iterator it = clients_.find(int_ip);
+  if (it != clients_.end()) {
+    clients_.erase(it);
+  }
+}
+
+NATSocketServer::Translator* NATSocketServer::Translator::FindClient(
+    const SocketAddress& int_ip) {
+  // See if we have the requested IP, or any of our children do.
+  return (clients_.find(int_ip) != clients_.end()) ?
+      this : nats_.FindClient(int_ip);
+}
+
+// NATSocketServer::TranslatorMap
+NATSocketServer::TranslatorMap::~TranslatorMap() {
+  for (TranslatorMap::iterator it = begin(); it != end(); ++it) {
+    delete it->second;
+  }
+}
+
+NATSocketServer::Translator* NATSocketServer::TranslatorMap::Get(
+    const SocketAddress& ext_ip) {
+  TranslatorMap::iterator it = find(ext_ip);
+  return (it != end()) ? it->second : NULL;
+}
+
+NATSocketServer::Translator* NATSocketServer::TranslatorMap::Add(
+    const SocketAddress& ext_ip, Translator* nat) {
+  (*this)[ext_ip] = nat;
+  return nat;
+}
+
+void NATSocketServer::TranslatorMap::Remove(
+    const SocketAddress& ext_ip) {
+  TranslatorMap::iterator it = find(ext_ip);
+  if (it != end()) {
+    delete it->second;
+    erase(it);
+  }
+}
+
+NATSocketServer::Translator* NATSocketServer::TranslatorMap::FindClient(
+    const SocketAddress& int_ip) {
+  Translator* nat = NULL;
+  for (TranslatorMap::iterator it = begin(); it != end() && !nat; ++it) {
+    nat = it->second->FindClient(int_ip);
+  }
+  return nat;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/natsocketfactory.h b/talk/base/natsocketfactory.h
new file mode 100644
index 0000000..d0566d3
--- /dev/null
+++ b/talk/base/natsocketfactory.h
@@ -0,0 +1,177 @@
+/*
+ * 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_NATSOCKETFACTORY_H_
+#define TALK_BASE_NATSOCKETFACTORY_H_
+
+#include <string>
+#include <map>
+#include <set>
+
+#include "talk/base/natserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/socketserver.h"
+
+namespace talk_base {
+
+const size_t kNATEncodedIPv4AddressSize = 8U;
+const size_t kNATEncodedIPv6AddressSize = 20U;
+
+// Used by the NAT socket implementation.
+class NATInternalSocketFactory {
+ public:
+  virtual ~NATInternalSocketFactory() {}
+  virtual AsyncSocket* CreateInternalSocket(int type,
+      const SocketAddress& local_addr, SocketAddress* nat_addr) = 0;
+};
+
+// Creates sockets that will send all traffic through a NAT, using an existing
+// NATServer instance running at nat_addr. The actual data is sent using sockets
+// from a socket factory, given to the constructor.
+class NATSocketFactory : public SocketFactory, public NATInternalSocketFactory {
+ public:
+  NATSocketFactory(SocketFactory* factory, const SocketAddress& nat_addr);
+
+  // SocketFactory implementation
+  virtual Socket* CreateSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+
+  // NATInternalSocketFactory implementation
+  virtual AsyncSocket* CreateInternalSocket(int type,
+      const SocketAddress& local_addr, SocketAddress* nat_addr);
+
+ private:
+  SocketFactory* factory_;
+  SocketAddress nat_addr_;
+  DISALLOW_EVIL_CONSTRUCTORS(NATSocketFactory);
+};
+
+// Creates sockets that will send traffic through a NAT depending on what
+// address they bind to. This can be used to simulate a client on a NAT sending
+// to a client that is not behind a NAT.
+// Note that the internal addresses of clients must be unique. This is because
+// there is only one socketserver per thread, and the Bind() address is used to
+// figure out which NAT (if any) the socket should talk to.
+//
+// Example with 3 NATs (2 cascaded), and 3 clients.
+// ss->AddTranslator("1.2.3.4", "192.168.0.1", NAT_ADDR_RESTRICTED);
+// ss->AddTranslator("99.99.99.99", "10.0.0.1", NAT_SYMMETRIC)->
+//     AddTranslator("10.0.0.2", "192.168.1.1", NAT_OPEN_CONE);
+// ss->GetTranslator("1.2.3.4")->AddClient("1.2.3.4", "192.168.0.2");
+// ss->GetTranslator("99.99.99.99")->AddClient("10.0.0.3");
+// ss->GetTranslator("99.99.99.99")->GetTranslator("10.0.0.2")->
+//     AddClient("192.168.1.2");
+class NATSocketServer : public SocketServer, public NATInternalSocketFactory {
+ public:
+  class Translator;
+  // holds a list of NATs
+  class TranslatorMap : private std::map<SocketAddress, Translator*> {
+   public:
+    ~TranslatorMap();
+    Translator* Get(const SocketAddress& ext_ip);
+    Translator* Add(const SocketAddress& ext_ip, Translator*);
+    void Remove(const SocketAddress& ext_ip);
+    Translator* FindClient(const SocketAddress& int_ip);
+  };
+
+  // a specific NAT
+  class Translator {
+   public:
+    Translator(NATSocketServer* server, NATType type,
+               const SocketAddress& int_addr, SocketFactory* ext_factory,
+               const SocketAddress& ext_addr);
+
+    SocketFactory* internal_factory() { return internal_factory_.get(); }
+    SocketAddress internal_address() const {
+      return nat_server_->internal_address();
+    }
+    SocketAddress internal_tcp_address() const {
+      return SocketAddress();  // nat_server_->internal_tcp_address();
+    }
+
+    Translator* GetTranslator(const SocketAddress& ext_ip);
+    Translator* AddTranslator(const SocketAddress& ext_ip,
+                              const SocketAddress& int_ip, NATType type);
+    void RemoveTranslator(const SocketAddress& ext_ip);
+
+    bool AddClient(const SocketAddress& int_ip);
+    void RemoveClient(const SocketAddress& int_ip);
+
+    // Looks for the specified client in this or a child NAT.
+    Translator* FindClient(const SocketAddress& int_ip);
+
+   private:
+    NATSocketServer* server_;
+    scoped_ptr<SocketFactory> internal_factory_;
+    scoped_ptr<NATServer> nat_server_;
+    TranslatorMap nats_;
+    std::set<SocketAddress> clients_;
+  };
+
+  explicit NATSocketServer(SocketServer* ss);
+
+  SocketServer* socketserver() { return server_; }
+  MessageQueue* queue() { return msg_queue_; }
+
+  Translator* GetTranslator(const SocketAddress& ext_ip);
+  Translator* AddTranslator(const SocketAddress& ext_ip,
+                            const SocketAddress& int_ip, NATType type);
+  void RemoveTranslator(const SocketAddress& ext_ip);
+
+  // SocketServer implementation
+  virtual Socket* CreateSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual void SetMessageQueue(MessageQueue* queue) {
+    msg_queue_ = queue;
+    server_->SetMessageQueue(queue);
+  }
+  virtual bool Wait(int cms, bool process_io) {
+    return server_->Wait(cms, process_io);
+  }
+  virtual void WakeUp() {
+    server_->WakeUp();
+  }
+
+  // NATInternalSocketFactory implementation
+  virtual AsyncSocket* CreateInternalSocket(int type,
+      const SocketAddress& local_addr, SocketAddress* nat_addr);
+
+ private:
+  SocketServer* server_;
+  MessageQueue* msg_queue_;
+  TranslatorMap nats_;
+  DISALLOW_EVIL_CONSTRUCTORS(NATSocketServer);
+};
+
+// Free-standing NAT helper functions.
+size_t PackAddressForNAT(char* buf, size_t buf_size,
+                         const SocketAddress& remote_addr);
+size_t UnpackAddressFromNAT(const char* buf, size_t buf_size,
+                            SocketAddress* remote_addr);
+}  // namespace talk_base
+
+#endif  // TALK_BASE_NATSOCKETFACTORY_H_
diff --git a/talk/base/nattypes.cc b/talk/base/nattypes.cc
new file mode 100644
index 0000000..290c3ad
--- /dev/null
+++ b/talk/base/nattypes.cc
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <cassert>
+
+#include "talk/base/nattypes.h"
+
+namespace talk_base {
+
+class SymmetricNAT : public NAT {
+public:
+  bool IsSymmetric() { return true; }
+  bool FiltersIP() { return true; }
+  bool FiltersPort() { return true; }
+};
+
+class OpenConeNAT : public NAT {
+public:
+  bool IsSymmetric() { return false; }
+  bool FiltersIP() { return false; }
+  bool FiltersPort() { return false; }
+};
+
+class AddressRestrictedNAT : public NAT {
+public:
+  bool IsSymmetric() { return false; }
+  bool FiltersIP() { return true; }
+  bool FiltersPort() { return false; }
+};
+
+class PortRestrictedNAT : public NAT {
+public:
+  bool IsSymmetric() { return false; }
+  bool FiltersIP() { return true; }
+  bool FiltersPort() { return true; }
+};
+
+NAT* NAT::Create(NATType type) {
+  switch (type) {
+  case NAT_OPEN_CONE:       return new OpenConeNAT();
+  case NAT_ADDR_RESTRICTED: return new AddressRestrictedNAT();
+  case NAT_PORT_RESTRICTED: return new PortRestrictedNAT();
+  case NAT_SYMMETRIC:       return new SymmetricNAT();
+  default: assert(0);       return 0;
+  }
+}
+
+} // namespace talk_base
diff --git a/talk/base/nattypes.h b/talk/base/nattypes.h
new file mode 100644
index 0000000..e9602c7
--- /dev/null
+++ b/talk/base/nattypes.h
@@ -0,0 +1,64 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_NATTYPE_H__
+#define TALK_BASE_NATTYPE_H__
+
+namespace talk_base {
+
+/* Identifies each type of NAT that can be simulated. */
+enum NATType {
+  NAT_OPEN_CONE,
+  NAT_ADDR_RESTRICTED,
+  NAT_PORT_RESTRICTED,
+  NAT_SYMMETRIC
+};
+
+// Implements the rules for each specific type of NAT.
+class NAT {
+public:
+  virtual ~NAT() { }
+
+  // Determines whether this NAT uses both source and destination address when
+  // checking whether a mapping already exists.
+  virtual bool IsSymmetric() = 0;
+
+  // Determines whether this NAT drops packets received from a different IP
+  // the one last sent to.
+  virtual bool FiltersIP() = 0;
+
+  // Determines whether this NAT drops packets received from a different port
+  // the one last sent to.
+  virtual bool FiltersPort() = 0;
+
+  // Returns an implementation of the given type of NAT.
+  static NAT* Create(NATType type);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_NATTYPE_H__
diff --git a/talk/base/nethelpers.cc b/talk/base/nethelpers.cc
index b408749..3961fc0 100644
--- a/talk/base/nethelpers.cc
+++ b/talk/base/nethelpers.cc
@@ -57,7 +57,7 @@
   }
 }
 
-#if defined(WIN32) || defined(ANDROID)
+#if defined(WIN32) || defined(ANDROID) || defined(OPENBSD)
 static hostent* DeepCopyHostent(const hostent* ent) {
   // Get the total number of bytes we need to copy, and allocate our buffer.
   int num_aliases = 0, num_addrs = 0;
@@ -165,9 +165,16 @@
   result = deep_copy;
 #endif
   *herrno = 0;
-#elif defined(OSX) || defined(IOS)
+#elif defined(OSX) || defined(IOS) || defined(FREEBSD)
   // Mac OS returns an object with everything allocated.
   result = getipnodebyname(hostname, AF_INET, AI_DEFAULT, herrno);
+#elif defined(OPENBSD)
+  hostent* ent = gethostbyname(hostname);
+  if (!ent) {
+    return NULL;
+  }
+  result = DeepCopyHostent(ent);
+  *herrno = 0;
 #else
 #error "I don't know how to do gethostbyname safely on your system."
 #endif
@@ -177,7 +184,7 @@
 // This function should mirror the above function, and free any resources
 // allocated by the above.
 void FreeHostEnt(hostent* host) {
-#if defined(OSX) || defined(IOS)
+#if defined(OSX) || defined(IOS) || defined(FREEBSD)
   freehostent(host);
 #elif defined(WIN32) || defined(POSIX)
   free(host);
@@ -186,4 +193,20 @@
 #endif
 }
 
+const char* inet_ntop(int af, const void *src, char* dst, socklen_t size) {
+#ifdef WIN32
+  return win32_inet_ntop(af, src, dst, size);
+#else
+  return ::inet_ntop(af, src, dst, size);
+#endif
+}
+
+int inet_pton(int af, const char* src, void *dst) {
+#ifdef WIN32
+  return win32_inet_pton(af, src, dst);
+#else
+  return ::inet_pton(af, src, dst);
+#endif
+}
+
 }  // namespace talk_base
diff --git a/talk/base/nethelpers.h b/talk/base/nethelpers.h
index 4cba9a9..ca97738 100644
--- a/talk/base/nethelpers.h
+++ b/talk/base/nethelpers.h
@@ -71,6 +71,11 @@
 hostent* SafeGetHostByName(const char* hostname, int* herrno);
 void FreeHostEnt(hostent* host);
 
+// talk_base namespaced wrappers for inet_ntop and inet_pton so we can avoid
+// the windows-native versions of these.
+const char* inet_ntop(int af, const void *src, char* dst, socklen_t size);
+int inet_pton(int af, const char* src, void *dst);
+
 }  // namespace talk_base
 
 #endif  // TALK_BASE_NETHELPERS_H_
diff --git a/talk/base/network.cc b/talk/base/network.cc
index 2feb361..9d1a83d 100644
--- a/talk/base/network.cc
+++ b/talk/base/network.cc
@@ -64,34 +64,6 @@
 // Fetch list of networks every two seconds.
 const int kNetworksUpdateIntervalMs = 2000;
 
-#ifdef POSIX
-// Gets the default gateway for the specified interface.
-uint32 GetDefaultGateway(const std::string& name) {
-#ifdef OSX
-  // TODO: /proc/net/route doesn't exist,
-  // Use ioctl to get the routing table
-  return 0xFFFFFFFF;
-#endif
-
-  uint32 gateway_ip = 0;
-
-  talk_base::FileStream fs;
-  if (fs.Open("/proc/net/route", "r", NULL)) {
-    std::string line;
-    while (fs.ReadLine(&line) == talk_base::SR_SUCCESS && gateway_ip == 0) {
-      char iface[16];
-      unsigned int ip, gw;
-      if (sscanf(line.c_str(), "%7s %8X %8X", iface, &ip, &gw) == 3 &&
-          name == iface && ip == 0) {
-        gateway_ip = ntohl(gw);
-      }
-    }
-  }
-
-  return gateway_ip;
-}
-#endif  // POSIX
-
 bool CompareNetworks(const Network* a, const Network* b) {
   return a->name() < b->name();
 }
@@ -146,11 +118,6 @@
         network->set_ip(list[i]->ip());
       }
 
-      if (network->gateway_ip() != list[i]->gateway_ip()) {
-        changed = true;
-        network->set_gateway_ip(list[i]->gateway_ip());
-      }
-
       delete list[i];
     }
 
@@ -199,10 +166,9 @@
     struct sockaddr_in* inaddr =
         reinterpret_cast<struct sockaddr_in*>(&ptr->ifr_ifru.ifru_addr);
     if (inaddr->sin_family == AF_INET) {
-      uint32 ip = ntohl(inaddr->sin_addr.s_addr);
+      IPAddress ip(inaddr->sin_addr);
       scoped_ptr<Network> network(
-          new Network(ptr->ifr_name, ptr->ifr_name, ip,
-                      GetDefaultGateway(ptr->ifr_name)));
+          new Network(ptr->ifr_name, ptr->ifr_name, ip));
       network->set_ignored(IsIgnoredNetwork(*network));
       if (include_ignored || !network->ignored()) {
         networks->push_back(network.release());
@@ -235,6 +201,8 @@
 
   scoped_array<char> buf(new char[len]);
   IP_ADAPTER_INFO *infos = reinterpret_cast<IP_ADAPTER_INFO *>(buf.get());
+  // TODO: GetAdaptersInfo is IPv4 only. Replace with GetAddressesInfo when
+  // IPv6 support is needed in Network.
   DWORD ret = GetAdaptersInfo(infos, &len);
   if (ret != NO_ERROR) {
     LOG_ERR_EX(LS_ERROR, ret) << "GetAdaptersInfo failed";
@@ -259,13 +227,13 @@
     name = ost.str();
     count++;
 #endif  // !_DEBUG
-
-    scoped_ptr<Network> network(new Network(name, info->Description,
-        SocketAddress::StringToIP(info->IpAddressList.IpAddress.String),
-        SocketAddress::StringToIP(info->GatewayList.IpAddress.String)));
-    network->set_ignored(IsIgnoredNetwork(*network));
-    if (include_ignored || !network->ignored()) {
-      networks->push_back(network.release());
+    IPAddress ip;
+    if (IPFromString(info->IpAddressList.IpAddress.String, &ip)) {
+      scoped_ptr<Network> network(new Network(name, info->Description, ip));
+      network->set_ignored(IsIgnoredNetwork(*network));
+      if (include_ignored || !network->ignored()) {
+        networks->push_back(network.release());
+      }
     }
   }
 
@@ -292,7 +260,10 @@
 #endif
 
   // Ignore any networks with a 0.x.y.z IP
-  return (network.ip() < 0x01000000);
+  if (network.ip().family() == AF_INET) {
+    return (network.ip().v4AddressAsHostOrderInteger() < 0x01000000);
+  }
+  return false;
 }
 
 void BasicNetworkManager::StartUpdating() {
@@ -342,17 +313,15 @@
     const Network* network = list[i];
     if (!network->ignored() || include_ignored) {
       LOG(LS_INFO) << network->ToString() << ": " << network->description()
-                   << ", Gateway="
-                   << SocketAddress::IPToString(network->gateway_ip())
                    << ((network->ignored()) ? ", Ignored" : "");
     }
   }
 }
 
 Network::Network(const std::string& name, const std::string& desc,
-                 uint32 ip, uint32 gateway_ip)
-    : name_(name), description_(desc), ip_(ip), gateway_ip_(gateway_ip),
-      ignored_(false), uniform_numerator_(0), uniform_denominator_(0),
+                 const IPAddress& ip)
+    : name_(name), description_(desc), ip_(ip), ignored_(false),
+      uniform_numerator_(0), uniform_denominator_(0),
       exponential_numerator_(0), exponential_denominator_(0) {
 }
 
@@ -361,7 +330,7 @@
   // Print out the first space-terminated token of the network desc, plus
   // the IP address.
   ss << "Net[" << description_.substr(0, description_.find(' '))
-     << ":" << SocketAddress::IPToString(ip_) << "]";
+     << ":" << ip_ << "]";
   return ss.str();
 }
 
diff --git a/talk/base/network.h b/talk/base/network.h
index 4b315c3..bb5f15d 100644
--- a/talk/base/network.h
+++ b/talk/base/network.h
@@ -34,6 +34,7 @@
 #include <vector>
 
 #include "talk/base/basictypes.h"
+#include "talk/base/ipaddress.h"
 #include "talk/base/messagehandler.h"
 #include "talk/base/sigslot.h"
 
@@ -135,8 +136,10 @@
 // Represents a Unix-type network interface, with a name and single address.
 class Network {
  public:
+  Network() : ip_(INADDR_ANY) {}
+
   Network(const std::string& name, const std::string& description,
-          uint32 ip, uint32 gateway_ip);
+          const IPAddress& ip);
 
   // Returns the index of this network.  This is considered the primary key
   // that identifies each network.
@@ -147,15 +150,11 @@
   const std::string& description() const { return description_; }
 
   // Identifies the current IP address used by this network.
-  uint32 ip() const { return ip_; }
-  void set_ip(uint32 ip) { ip_ = ip; }
+  const IPAddress& ip() const { return ip_; }
+  void set_ip(const IPAddress& ip) { ip_ = ip; }
 
-  // Identifies the current gateway IP address used by this network.
-  uint32 gateway_ip() const { return gateway_ip_; }
-  void set_gateway_ip(uint32 ip) { gateway_ip_ = ip; }
-
-  // Indicates whether this network should be ignored, perhaps because the
-  // IP/gateway is 0, or the interface is one we know is invalid.
+  // Indicates whether this network should be ignored, perhaps because
+  // the IP is 0, or the interface is one we know is invalid.
   bool ignored() const { return ignored_; }
   void set_ignored(bool ignored) { ignored_ = ignored; }
 
@@ -167,8 +166,7 @@
 
   std::string name_;
   std::string description_;
-  uint32 ip_;
-  uint32 gateway_ip_;
+  IPAddress ip_;
   bool ignored_;
   SessionList sessions_;
   double uniform_numerator_;
diff --git a/talk/base/network_unittest.cc b/talk/base/network_unittest.cc
index a55ba2f..d48abbb 100644
--- a/talk/base/network_unittest.cc
+++ b/talk/base/network_unittest.cc
@@ -33,15 +33,10 @@
 
 // A network that should not be ignored.
 static const Network kNetwork1("test1", "Test Network Adapter 1",
-                               0x12345678, 0x12345601);
+                               IPAddress(0x12345678));
 // A network that should be ignored (IP is 0.1.0.4).
 static const Network kNetwork2("test2", "Test Network Adapter 2",
-                               0x00010004, 0x01000000);
-// A network that should not be ignored (IP is valid, but gateway is 0.0.0.1).
-// Previously, we attempted to ignore networks with no default gateway,
-// but if an explicit route is set, no default gateway is needed.
-static const Network kNetwork3("test3", "Test Network Adapter 3",
-                               0x55667788, 0x01000000);
+                               IPAddress(0x00010004));
 
 class NetworkTest : public testing::Test, public sigslot::has_slots<>  {
  public:
@@ -63,6 +58,13 @@
     return BasicNetworkManager::IsIgnoredNetwork(network);
   }
 
+  NetworkManager::NetworkList GetNetworks(
+      const BasicNetworkManager& network_manager, bool include_ignored) {
+    NetworkManager::NetworkList list;
+    network_manager.CreateNetworks(include_ignored, &list);
+    return list;
+  }
+
  protected:
   bool callback_called_;
 };
@@ -71,8 +73,7 @@
 TEST_F(NetworkTest, TestNetworkConstruct) {
   EXPECT_EQ("test1", kNetwork1.name());
   EXPECT_EQ("Test Network Adapter 1", kNetwork1.description());
-  EXPECT_EQ(0x12345678U, kNetwork1.ip());
-  EXPECT_EQ(0x12345601U, kNetwork1.gateway_ip());
+  EXPECT_EQ(IPAddress(0x12345678U), kNetwork1.ip());
   EXPECT_FALSE(kNetwork1.ignored());
 }
 
@@ -80,7 +81,41 @@
 TEST_F(NetworkTest, TestNetworkIgnore) {
   EXPECT_FALSE(IsIgnoredNetwork(kNetwork1));
   EXPECT_TRUE(IsIgnoredNetwork(kNetwork2));
-  EXPECT_FALSE(IsIgnoredNetwork(kNetwork3));
+}
+
+TEST_F(NetworkTest, TestCreateNetworks) {
+  BasicNetworkManager manager;
+  NetworkManager::NetworkList result = GetNetworks(manager, true);
+  // We should be able to bind to any addresses we find.
+  // (Excluding IPv6 link-local for now, as we don't (yet) record scope ids.)
+  NetworkManager::NetworkList::iterator it;
+  for (it = result.begin();
+       it != result.end();
+       ++it) {
+    sockaddr_storage storage;
+    memset(&storage, 0, sizeof(storage));
+    IPAddress ip = (*it)->ip();
+    // This condition excludes FE80::/16, i.e. IPv6 link-local addresses. These
+    // require their scope id to be known. Remove when scope ids are supported.
+    if (!(ip.family() == AF_INET6 && IPIsPrivate(ip) && !IPIsLoopback(ip))) {
+      SocketAddress bindaddress(ip, 0);
+      // TODO: Make this use talk_base::AsyncSocket once it supports IPv6.
+      int fd = socket(ip.family(), SOCK_STREAM, IPPROTO_TCP);
+      if (fd > 0) {
+        size_t ipsize = bindaddress.ToSockAddrStorage(&storage);
+        EXPECT_GE(ipsize, 0U);
+        int success = ::bind(fd,
+                             reinterpret_cast<sockaddr*>(&storage),
+                             ipsize);
+        EXPECT_EQ(0, success);
+#ifdef WIN32
+        closesocket(fd);
+#else
+        close(fd);
+#endif
+      }
+    }
+  }
 }
 
 // Test that UpdateNetworks succeeds.
diff --git a/talk/base/openssladapter.cc b/talk/base/openssladapter.cc
index 4208b79..ea40172 100644
--- a/talk/base/openssladapter.cc
+++ b/talk/base/openssladapter.cc
@@ -747,6 +747,7 @@
 
   X509_free(certificate);
 
+  // This should only ever be turned on for debugging and development.
   if (!ok && ignore_bad_cert) {
     LOG(LS_WARNING) << "TLS certificate check FAILED.  "
       << "Allowing connection anyway.";
@@ -840,6 +841,7 @@
     }
   }
 
+  // Should only be used for debugging and development.
   if (!ok && stream->ignore_bad_cert()) {
     LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain";
     ok = 1;
diff --git a/talk/base/opensslidentity.cc b/talk/base/opensslidentity.cc
index a9d94b2..68b113b 100644
--- a/talk/base/opensslidentity.cc
+++ b/talk/base/opensslidentity.cc
@@ -147,6 +147,7 @@
 static void LogSSLErrors(const std::string& prefix) {
   char error_buf[200];
   unsigned long err;
+
   while ((err = ERR_get_error())) {
     ERR_error_string_n(err, error_buf, sizeof(error_buf));
     LOG(LS_ERROR) << prefix << ": " << error_buf << "\n";
@@ -224,6 +225,74 @@
     return NULL;
 }
 
+bool OpenSSLCertificate::GetDigestLength(const std::string &algorithm,
+                                         std::size_t *length) {
+  const EVP_MD *md;
+
+  if (!GetDigestEVP(algorithm, &md))
+    return false;
+
+  *length = EVP_MD_size(md);
+
+  return true;
+}
+
+bool OpenSSLCertificate::ComputeDigest(const std::string &algorithm,
+                                       unsigned char *digest,
+                                       std::size_t size,
+                                       std::size_t *length) const {
+  return ComputeDigest(x509_, algorithm, digest, size, length);
+}
+
+bool OpenSSLCertificate::ComputeDigest(const X509 *x509,
+                                       const std::string &algorithm,
+                                       unsigned char *digest,
+                                       std::size_t size,
+                                       std::size_t *length) {
+  const EVP_MD *md;
+  unsigned int n;
+
+  if (!GetDigestEVP(algorithm, &md))
+    return false;
+
+  if (size < static_cast<size_t>(EVP_MD_size(md)))
+    return false;
+
+  X509_digest(x509, md, digest, &n);
+
+  *length = n;
+
+  return true;
+}
+
+
+bool OpenSSLCertificate::GetDigestEVP(const std::string &algorithm,
+                                      const EVP_MD **mdp) {
+  const EVP_MD *md;
+  if (algorithm == DIGEST_SHA_1) {
+    md = EVP_sha1();
+  }
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+  else if (algorithm == DIGEST_SHA_224) {
+    md = EVP_sha224();
+  } else if (algorithm == DIGEST_SHA_256) {
+    md = EVP_sha256();
+  } else if (algorithm == DIGEST_SHA_384) {
+    md = EVP_sha384();
+  } else if (algorithm == DIGEST_SHA_512) {
+    md = EVP_sha512();
+  }
+#endif
+  else {
+    return false;
+  }
+
+  // Can't happen
+  ASSERT(EVP_MD_size(md) >= 20);
+  *mdp = md;
+  return true;
+}
+
 OpenSSLCertificate::~OpenSSLCertificate() {
   X509_free(x509_);
 }
diff --git a/talk/base/opensslidentity.h b/talk/base/opensslidentity.h
index 7cac419..f642736 100644
--- a/talk/base/opensslidentity.h
+++ b/talk/base/opensslidentity.h
@@ -87,6 +87,22 @@
 
   virtual std::string ToPEMString() const;
 
+  // Compute the digest of the certificate given algorithm
+  virtual bool ComputeDigest(const std::string &algorithm,
+                             unsigned char *digest, std::size_t size,
+                             std::size_t *length) const;
+
+  // Compute the digest of a certificate as an X509 *
+  static bool ComputeDigest(const X509 *x509,
+                             const std::string &algorithm,
+                             unsigned char *digest,
+                             std::size_t size,
+                             std::size_t *length);
+
+  // Helper function to get the length of a digest
+  static bool GetDigestLength(const std::string &algorithm,
+                              std::size_t *length);
+
  private:
   explicit OpenSSLCertificate(X509* x509) : x509_(x509) {
     ASSERT(x509_ != NULL);
@@ -95,6 +111,10 @@
 
   X509* x509_;
 
+  // Helper function to look up a digest
+  static bool GetDigestEVP(const std::string &algorithm,
+                           const EVP_MD **md);
+
   DISALLOW_EVIL_CONSTRUCTORS(OpenSSLCertificate);
 };
 
@@ -132,6 +152,7 @@
   DISALLOW_EVIL_CONSTRUCTORS(OpenSSLIdentity);
 };
 
+
 }  // namespace talk_base
 
 #endif  // TALK_BASE_OPENSSLIDENTITY_H__
diff --git a/talk/base/opensslstreamadapter.cc b/talk/base/opensslstreamadapter.cc
index 6a34e13..be061ad 100644
--- a/talk/base/opensslstreamadapter.cc
+++ b/talk/base/opensslstreamadapter.cc
@@ -40,15 +40,41 @@
 #include <openssl/ssl.h>
 #include <openssl/x509v3.h>
 
+#include <vector>
+
 #include "talk/base/common.h"
 #include "talk/base/logging.h"
 #include "talk/base/stream.h"
 #include "talk/base/openssladapter.h"
 #include "talk/base/opensslidentity.h"
 #include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
 
 namespace talk_base {
 
+#if (OPENSSL_VERSION_NUMBER >= 0x10001000L)
+#define HAVE_DTLS_SRTP
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
+#define HAVE_DTLS
+#endif
+
+#ifdef HAVE_DTLS_SRTP
+// SRTP cipher suite table
+struct SrtpCipherMapEntry {
+  const char* external_name;
+  const char* internal_name;
+};
+
+// This isn't elegant, but it's better than an external reference
+static SrtpCipherMapEntry SrtpCipherMap[] = {
+  {"AES_CM_128_HMAC_SHA1_80", "SRTP_AES128_CM_SHA1_80"},
+  {"AES_CM_128_HMAC_SHA1_32", "SRTP_AES128_CM_SHA1_32"},
+  {NULL, NULL}
+};
+#endif
+
 //////////////////////////////////////////////////////////////////////
 // StreamBIO
 //////////////////////////////////////////////////////////////////////
@@ -166,7 +192,8 @@
       role_(SSL_CLIENT),
       ssl_read_needs_write_(false), ssl_write_needs_read_(false),
       ssl_(NULL), ssl_ctx_(NULL),
-      custom_verification_succeeded_(false) {
+      custom_verification_succeeded_(false),
+      ssl_mode_(SSL_MODE_TLS) {
 }
 
 OpenSSLStreamAdapter::~OpenSSLStreamAdapter() {
@@ -178,16 +205,130 @@
   identity_.reset(static_cast<OpenSSLIdentity*>(identity));
 }
 
-void OpenSSLStreamAdapter::SetServerRole() {
-  role_ = SSL_SERVER;
+void OpenSSLStreamAdapter::SetServerRole(SSLRole role) {
+  role_ = role;
 }
 
 void OpenSSLStreamAdapter::SetPeerCertificate(SSLCertificate* cert) {
   ASSERT(peer_certificate_.get() == NULL);
+  ASSERT(peer_certificate_digest_algorithm_.empty());
   ASSERT(ssl_server_name_.empty());
   peer_certificate_.reset(static_cast<OpenSSLCertificate*>(cert));
 }
 
+bool OpenSSLStreamAdapter::SetPeerCertificateDigest(const std::string
+                                                    &digest_alg,
+                                                    const unsigned char*
+                                                    digest_val,
+                                                    size_t digest_len) {
+  ASSERT(peer_certificate_.get() == NULL);
+  ASSERT(peer_certificate_digest_algorithm_.size() == 0);
+  ASSERT(ssl_server_name_.empty());
+  size_t expected_len;
+
+  if (!OpenSSLCertificate::GetDigestLength(digest_alg, &expected_len)) {
+    LOG(LS_WARNING) << "Unknown digest algorithm: " << digest_alg;
+    return false;
+  }
+  if (expected_len != digest_len)
+    return false;
+
+  peer_certificate_digest_value_.SetData(digest_val, digest_len);
+  peer_certificate_digest_algorithm_ = digest_alg;
+
+  return true;
+}
+
+// Key Extractor interface
+bool OpenSSLStreamAdapter::ExportKeyingMaterial(const std::string& label,
+                                                const uint8* context,
+                                                size_t context_len,
+                                                bool use_context,
+                                                uint8* result,
+                                                size_t result_len) {
+#ifdef HAVE_DTLS_SRTP
+  int i;
+
+  i = SSL_export_keying_material(ssl_, result, result_len,
+                                 label.c_str(), label.length(),
+                                 const_cast<uint8 *>(context),
+                                 context_len, use_context);
+
+  if (i != 1)
+    return false;
+
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool OpenSSLStreamAdapter::SetDtlsSrtpCiphers(
+    const std::vector<std::string>& ciphers) {
+  std::string internal_ciphers;
+
+  if (state_ != SSL_NONE)
+    return false;
+
+#ifdef HAVE_DTLS_SRTP
+  for (std::vector<std::string>::const_iterator cipher = ciphers.begin();
+       cipher != ciphers.end(); ++cipher) {
+    bool found = false;
+    for (SrtpCipherMapEntry *entry = SrtpCipherMap; entry->internal_name;
+         ++entry) {
+      if (*cipher == entry->external_name) {
+        found = true;
+        if (!internal_ciphers.empty())
+          internal_ciphers += ":";
+        internal_ciphers += entry->internal_name;
+        break;
+      }
+    }
+
+    if (!found) {
+      LOG(LS_ERROR) << "Could not find cipher: " << *cipher;
+      return false;
+    }
+  }
+
+  if (internal_ciphers.empty())
+    return false;
+
+  srtp_ciphers_ = internal_ciphers;
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool OpenSSLStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) {
+#ifdef HAVE_DTLS_SRTP
+  ASSERT(state_ == SSL_CONNECTED);
+  if (state_ != SSL_CONNECTED)
+    return false;
+
+  SRTP_PROTECTION_PROFILE *srtp_profile =
+      SSL_get_selected_srtp_profile(ssl_);
+
+  if (!srtp_profile)
+    return NULL;
+
+  for (SrtpCipherMapEntry *entry = SrtpCipherMap;
+       entry->internal_name; ++entry) {
+    if (!strcmp(entry->internal_name, srtp_profile->name)) {
+      *cipher = entry->external_name;
+      return true;
+    }
+  }
+
+  ASSERT(false);  // This should never happen
+
+  return false;
+#else
+  return false;
+#endif
+}
+
 int OpenSSLStreamAdapter::StartSSLWithServer(const char* server_name) {
   ASSERT(server_name != NULL && server_name[0] != '\0');
   ssl_server_name_ = server_name;
@@ -200,6 +341,11 @@
   return StartSSL();
 }
 
+void OpenSSLStreamAdapter::SetMode(SSLMode mode) {
+  ASSERT(state_ == SSL_NONE);
+  ssl_mode_ = mode;
+}
+
 //
 // StreamInterface Implementation
 //
@@ -238,7 +384,8 @@
   ssl_write_needs_read_ = false;
 
   int code = SSL_write(ssl_, data, data_len);
-  switch (SSL_get_error(ssl_, code)) {
+  int ssl_error = SSL_get_error(ssl_, code);
+  switch (ssl_error) {
   case SSL_ERROR_NONE:
     LOG(LS_INFO) << " -- success";
     ASSERT(0 < code && static_cast<unsigned>(code) <= data_len);
@@ -255,7 +402,7 @@
 
   case SSL_ERROR_ZERO_RETURN:
   default:
-    Error("SSL_write", (code ? code : -1), false);
+    Error("SSL_write", (ssl_error ? ssl_error : -1), false);
     if (error)
       *error = ssl_error_code_;
     return SR_ERROR;
@@ -298,12 +445,26 @@
   ssl_read_needs_write_ = false;
 
   int code = SSL_read(ssl_, data, data_len);
-  switch (SSL_get_error(ssl_, code)) {
+  int ssl_error = SSL_get_error(ssl_, code);
+  switch (ssl_error) {
     case SSL_ERROR_NONE:
       LOG(LS_INFO) << " -- success";
       ASSERT(0 < code && static_cast<unsigned>(code) <= data_len);
       if (read)
         *read = code;
+
+      if (ssl_mode_ == SSL_MODE_DTLS) {
+        // Enforce atomic reads -- this is a short read
+        unsigned int pending = SSL_pending(ssl_);
+
+        if (pending) {
+          LOG(LS_INFO) << " -- short DTLS read. flushing";
+          FlushInput(pending);
+          if (error)
+            *error = SSE_MSG_TRUNC;
+          return SR_ERROR;
+        }
+      }
       return SR_SUCCESS;
     case SSL_ERROR_WANT_READ:
       LOG(LS_INFO) << " -- error want read";
@@ -318,7 +479,7 @@
       break;
     default:
       LOG(LS_INFO) << " -- error " << code;
-      Error("SSL_read", (code ? code : -1), false);
+      Error("SSL_read", (ssl_error ? ssl_error : -1), false);
       if (error)
         *error = ssl_error_code_;
       return SR_ERROR;
@@ -326,6 +487,27 @@
   // not reached
 }
 
+void OpenSSLStreamAdapter::FlushInput(unsigned int left) {
+  unsigned char buf[2048];
+
+  while (left) {
+    // This should always succeed
+    int toread = (sizeof(buf) < left) ? sizeof(buf) : left;
+    int code = SSL_read(ssl_, buf, toread);
+
+    int ssl_error = SSL_get_error(ssl_, code);
+    ASSERT(ssl_error == SSL_ERROR_NONE);
+
+    if (ssl_error != SSL_ERROR_NONE) {
+      LOG(LS_INFO) << " -- error " << code;
+      Error("SSL_read", (ssl_error ? ssl_error : -1), false);
+      return;
+    }
+    LOG(LS_INFO) << " -- flushed " << code << " bytes";
+    left -= code;
+  }
+}
+
 void OpenSSLStreamAdapter::Close() {
   Cleanup();
   ASSERT(state_ == SSL_CLOSED || state_ == SSL_ERROR);
@@ -333,7 +515,7 @@
 }
 
 StreamState OpenSSLStreamAdapter::GetState() const {
-  switch(state_) {
+  switch (state_) {
     case SSL_WAIT:
     case SSL_CONNECTING:
       return SS_OPENING;
@@ -395,7 +577,7 @@
     ASSERT(signal_error == 0);
     signal_error = err;
   }
-  if(events_to_signal)
+  if (events_to_signal)
     StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error);
 }
 
@@ -420,7 +602,9 @@
   ASSERT(state_ == SSL_CONNECTING);
   // The underlying stream has open. If we are in peer-to-peer mode
   // then a peer certificate must have been specified by now.
-  ASSERT(!ssl_server_name_.empty() || peer_certificate_.get() != NULL);
+  ASSERT(!ssl_server_name_.empty() ||
+         peer_certificate_.get() != NULL ||
+         !peer_certificate_digest_algorithm_.empty());
   LOG(LS_INFO) << "BeginSSL: "
                << (!ssl_server_name_.empty() ? ssl_server_name_ :
                                                "with peer");
@@ -458,14 +642,19 @@
   LOG(LS_INFO) << "ContinueSSL";
   ASSERT(state_ == SSL_CONNECTING);
 
+  // Clear the DTLS timer
+  Thread::Current()->Clear(this, MSG_TIMEOUT);
+
   int code = (role_ == SSL_CLIENT) ? SSL_connect(ssl_) : SSL_accept(ssl_);
-  switch (SSL_get_error(ssl_, code)) {
+  int ssl_error;
+  switch (ssl_error = SSL_get_error(ssl_, code)) {
     case SSL_ERROR_NONE:
       LOG(LS_INFO) << " -- success";
 
       if (!SSLPostConnectionCheck(ssl_, ssl_server_name_.c_str(),
                                   peer_certificate_.get() != NULL
-                                  ? peer_certificate_->x509() : NULL)) {
+                                  ? peer_certificate_->x509() : NULL,
+                                  peer_certificate_digest_algorithm_)) {
         LOG(LS_ERROR) << "TLS post connection check failed";
         return -1;
       }
@@ -474,8 +663,17 @@
       StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0);
       break;
 
-    case SSL_ERROR_WANT_READ:
-      LOG(LS_INFO) << " -- error want read";
+    case SSL_ERROR_WANT_READ: {
+        LOG(LS_INFO) << " -- error want read";
+#ifdef HAVE_DTLS
+        struct timeval timeout;
+        if (DTLSv1_get_timeout(ssl_, &timeout)) {
+          int delay = timeout.tv_sec * 1000 + timeout.tv_usec/1000;
+
+          Thread::Current()->PostDelayed(delay, this, MSG_TIMEOUT, 0);
+        }
+#endif
+      }
       break;
 
     case SSL_ERROR_WANT_WRITE:
@@ -485,7 +683,7 @@
     case SSL_ERROR_ZERO_RETURN:
     default:
       LOG(LS_INFO) << " -- error " << code;
-      return (code != 0) ? code : -1;
+      return (ssl_error != 0) ? ssl_error : -1;
   }
 
   return 0;
@@ -519,11 +717,43 @@
   }
   identity_.reset();
   peer_certificate_.reset();
+
+  // Clear the DTLS timer
+  Thread::Current()->Clear(this, MSG_TIMEOUT);
+}
+
+
+void OpenSSLStreamAdapter::OnMessage(Message* msg) {
+  // Process our own messages and then pass others to the superclass
+  if (MSG_TIMEOUT == msg->message_id) {
+    LOG(LS_INFO) << "DTLS timeout expired";
+#ifdef HAVE_DTLS
+    DTLSv1_handle_timeout(ssl_);
+#endif
+    ContinueSSL();
+  } else {
+    StreamInterface::OnMessage(msg);
+  }
 }
 
 SSL_CTX* OpenSSLStreamAdapter::SetupSSLContext() {
-  SSL_CTX* ctx = SSL_CTX_new(role_ == SSL_CLIENT ? TLSv1_client_method()
-                             : TLSv1_server_method());
+  SSL_CTX *ctx = NULL;
+
+  if (role_ == SSL_CLIENT) {
+#ifdef HAVE_DTLS
+    ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ?
+        DTLSv1_client_method() : TLSv1_client_method());
+#else
+    ctx = SSL_CTX_new(TLSv1_client_method());
+#endif
+  } else {
+#ifdef HAVE_DTLS
+    ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ?
+        DTLSv1_server_method() : TLSv1_server_method());
+#else
+    ctx = SSL_CTX_new(TLSv1_server_method());
+#endif
+  }
   if (ctx == NULL)
     return NULL;
 
@@ -534,7 +764,7 @@
 
   if (peer_certificate_.get() == NULL) {  // traditional mode
     // Add the root cert to the SSL context
-    if(!OpenSSLAdapter::ConfigureTrustedRootCertificates(ctx)) {
+    if (!OpenSSLAdapter::ConfigureTrustedRootCertificates(ctx)) {
       SSL_CTX_free(ctx);
       return NULL;
     }
@@ -553,6 +783,15 @@
   SSL_CTX_set_verify_depth(ctx, 4);
   SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
 
+#ifdef HAVE_DTLS_SRTP
+  if (!srtp_ciphers_.empty()) {
+    if (SSL_CTX_set_tlsext_use_srtp(ctx, srtp_ciphers_.c_str())) {
+      SSL_CTX_free(ctx);
+      return NULL;
+    }
+  }
+#endif
+
   return ctx;
 }
 
@@ -596,6 +835,29 @@
       LOG(LS_INFO) << "Accepted self-signed peer certificate authority";
       ok = 1;
     }
+  } else if (!ok && !stream->peer_certificate_digest_algorithm_.empty()) {
+    X509* cert = X509_STORE_CTX_get_current_cert(store);
+    int err = X509_STORE_CTX_get_error(store);
+
+    // peer-to-peer mode: allow the certificate to be self-signed,
+    // assuming it matches the digest that was specified.
+    if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) {
+      unsigned char digest[EVP_MAX_MD_SIZE];
+      std::size_t digest_length;
+
+      if (OpenSSLCertificate::
+         ComputeDigest(cert,
+                       stream->peer_certificate_digest_algorithm_,
+                       digest, sizeof(digest),
+                       &digest_length)) {
+        Buffer computed_digest(digest, digest_length);
+        if (computed_digest == stream->peer_certificate_digest_value_) {
+          LOG(LS_INFO) <<
+              "Accepted self-signed peer certificate authority";
+          ok = 1;
+        }
+      }
+    }
   } else if (!ok && OpenSSLAdapter::custom_verify_callback_) {
     // this applies only in traditional mode
     void* cert =
@@ -619,10 +881,12 @@
 // sample in chapter 5
 bool OpenSSLStreamAdapter::SSLPostConnectionCheck(SSL* ssl,
                                                   const char* server_name,
-                                                  const X509* peer_cert) {
+                                                  const X509* peer_cert,
+                                                  const std::string
+                                                  &peer_digest) {
   ASSERT(server_name != NULL);
   bool ok;
-  if(server_name[0] != '\0') {  // traditional mode
+  if (server_name[0] != '\0') {  // traditional mode
     ok = OpenSSLAdapter::VerifyServerName(ssl, server_name, ignore_bad_cert());
 
     if (ok) {
@@ -630,7 +894,7 @@
             custom_verification_succeeded_);
     }
   } else {  // peer-to-peer mode
-    ASSERT(peer_cert != NULL);
+    ASSERT((peer_cert != NULL) || (!peer_digest.empty()));
     // no server name validation
     ok = true;
   }
@@ -645,6 +909,30 @@
   return ok;
 }
 
+bool OpenSSLStreamAdapter::HaveDtls() {
+#ifdef HAVE_DTLS
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool OpenSSLStreamAdapter::HaveDtlsSrtp() {
+#ifdef HAVE_DTLS_SRTP
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool OpenSSLStreamAdapter::HaveExporter() {
+#ifdef HAVE_DTLS_SRTP
+  return true;
+#else
+  return false;
+#endif
+}
+
 }  // namespace talk_base
 
 #endif  // HAVE_OPENSSL_SSL_H
diff --git a/talk/base/opensslstreamadapter.h b/talk/base/opensslstreamadapter.h
index 16ec751..8e92a10 100644
--- a/talk/base/opensslstreamadapter.h
+++ b/talk/base/opensslstreamadapter.h
@@ -29,6 +29,9 @@
 #define TALK_BASE_OPENSSLSTREAMADAPTER_H__
 
 #include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
 #include "talk/base/sslstreamadapter.h"
 #include "talk/base/opensslidentity.h"
 
@@ -75,11 +78,17 @@
   virtual ~OpenSSLStreamAdapter();
 
   virtual void SetIdentity(SSLIdentity* identity);
-  virtual void SetServerRole();
+
+  // Default argument is for compatibility
+  virtual void SetServerRole(SSLRole role = SSL_SERVER);
   virtual void SetPeerCertificate(SSLCertificate* cert);
+  virtual bool SetPeerCertificateDigest(const std::string& digest_alg,
+                                        const unsigned char* digest_val,
+                                        size_t digest_len);
 
   virtual int StartSSLWithServer(const char* server_name);
   virtual int StartSSLWithPeer();
+  virtual void SetMode(SSLMode mode);
 
   virtual StreamResult Read(void* data, size_t data_len,
                             size_t* read, int* error);
@@ -88,6 +97,24 @@
   virtual void Close();
   virtual StreamState GetState() const;
 
+  // Key Extractor interface
+  virtual bool ExportKeyingMaterial(const std::string& label,
+                                    const uint8* context,
+                                    size_t context_len,
+                                    bool use_context,
+                                    uint8* result,
+                                    size_t result_len);
+
+
+  // DTLS-SRTP interface
+  virtual bool SetDtlsSrtpCiphers(const std::vector<std::string>& ciphers);
+  virtual bool GetDtlsSrtpCipher(std::string* cipher);
+
+  // Capabilities interfaces
+  static bool HaveDtls();
+  static bool HaveDtlsSrtp();
+  static bool HaveExporter();
+
  protected:
   virtual void OnEvent(StreamInterface* stream, int events, int err);
 
@@ -102,9 +129,8 @@
     SSL_ERROR,  // some SSL error occurred, stream is closed
     SSL_CLOSED  // Clean close
   };
-  enum SSLRole {
-    SSL_CLIENT, SSL_SERVER
-  };
+
+  enum { MSG_TIMEOUT = MSG_MAX+1};
 
   // The following three methods return 0 on success and a negative
   // error code on failure. The error code may be from OpenSSL or -1
@@ -129,11 +155,18 @@
   void Error(const char* context, int err, bool signal);
   void Cleanup();
 
+  // Override MessageHandler
+  virtual void OnMessage(Message* msg);
+
+  // Flush the input buffers by reading left bytes (for DTLS)
+  void FlushInput(unsigned int left);
+
   // SSL library configuration
   SSL_CTX* SetupSSLContext();
   // SSL verification check
   bool SSLPostConnectionCheck(SSL* ssl, const char* server_name,
-                              const X509* peer_cert);
+                              const X509* peer_cert,
+                              const std::string& peer_digest);
   // SSL certification verification error handler, called back from
   // the openssl library. Returns an int interpreted as a boolean in
   // the C style: zero means verification failure, non-zero means
@@ -151,17 +184,28 @@
 
   SSL* ssl_;
   SSL_CTX* ssl_ctx_;
-  // in traditional mode, the server name that the server's certificate
-  // must specify. Empty in peer-to-peer mode.
+
   // Our key and certificate, mostly useful in peer-to-peer mode.
   scoped_ptr<OpenSSLIdentity> identity_;
+  // in traditional mode, the server name that the server's certificate
+  // must specify. Empty in peer-to-peer mode.
   std::string ssl_server_name_;
   // In peer-to-peer mode, the certificate that the peer must
   // present. Empty in traditional mode.
   scoped_ptr<OpenSSLCertificate> peer_certificate_;
+  // In peer-to-peer mode, the digest of the certificate that
+  // the peer must present.
+  Buffer peer_certificate_digest_value_;
+  std::string peer_certificate_digest_algorithm_;
 
   // OpenSSLAdapter::custom_verify_callback_ result
   bool custom_verification_succeeded_;
+
+  // The DtlsSrtp ciphers
+  std::string srtp_ciphers_;
+
+  // Do DTLS or not
+  SSLMode ssl_mode_;
 };
 
 /////////////////////////////////////////////////////////////////////////////
diff --git a/talk/base/optionsfile.cc b/talk/base/optionsfile.cc
new file mode 100644
index 0000000..82a5c86
--- /dev/null
+++ b/talk/base/optionsfile.cc
@@ -0,0 +1,201 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/optionsfile.h"
+
+#include <ctype.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+OptionsFile::OptionsFile(const std::string &path) : path_(path) {
+}
+
+bool OptionsFile::Load() {
+  options_.clear();
+  // Open file.
+  FileStream stream;
+  int err;
+  if (!stream.Open(path_, "r", &err)) {
+    LOG_F(LS_WARNING) << "Could not open file, err=" << err;
+    // We do not consider this an error because we expect there to be no file
+    // until the user saves a setting.
+    return true;
+  }
+  // Read in all its data.
+  std::string line;
+  StreamResult res;
+  for (;;) {
+    res = stream.ReadLine(&line);
+    if (res != SR_SUCCESS) {
+      break;
+    }
+    size_t equals_pos = line.find('=');
+    if (equals_pos == std::string::npos) {
+      // We do not consider this an error. Instead we ignore the line and
+      // keep going.
+      LOG_F(LS_WARNING) << "Ignoring malformed line in " << path_;
+      continue;
+    }
+    std::string key(line, 0, equals_pos);
+    std::string value(line, equals_pos + 1, line.length() - (equals_pos + 1));
+    options_[key] = value;
+  }
+  if (res != SR_EOS) {
+    LOG_F(LS_ERROR) << "Error when reading from file";
+    return false;
+  } else {
+    return true;
+  }
+}
+
+bool OptionsFile::Save() {
+  // Open file.
+  FileStream stream;
+  int err;
+  if (!stream.Open(path_, "w", &err)) {
+    LOG_F(LS_ERROR) << "Could not open file, err=" << err;
+    return false;
+  }
+  // Write out all the data.
+  StreamResult res = SR_SUCCESS;
+  size_t written;
+  int error;
+  for (OptionsMap::const_iterator i = options_.begin(); i != options_.end();
+       ++i) {
+    res = stream.WriteAll(i->first.c_str(), i->first.length(), &written,
+        &error);
+    if (res != SR_SUCCESS) {
+      break;
+    }
+    res = stream.WriteAll("=", 1, &written, &error);
+    if (res != SR_SUCCESS) {
+      break;
+    }
+    res = stream.WriteAll(i->second.c_str(), i->second.length(), &written,
+        &error);
+    if (res != SR_SUCCESS) {
+      break;
+    }
+    res = stream.WriteAll("\n", 1, &written, &error);
+    if (res != SR_SUCCESS) {
+      break;
+    }
+  }
+  if (res != SR_SUCCESS) {
+    LOG_F(LS_ERROR) << "Unable to write to file";
+    return false;
+  } else {
+    return true;
+  }
+}
+
+bool OptionsFile::IsLegalName(const std::string &name) {
+  for (size_t pos = 0; pos < name.length(); ++pos) {
+    if (name[pos] == '\n' || name[pos] == '\\' || name[pos] == '=') {
+      // Illegal character.
+      LOG(LS_WARNING) << "Ignoring operation for illegal option " << name;
+      return false;
+    }
+  }
+  return true;
+}
+
+bool OptionsFile::IsLegalValue(const std::string &value) {
+  for (size_t pos = 0; pos < value.length(); ++pos) {
+    if (value[pos] == '\n' || value[pos] == '\\') {
+      // Illegal character.
+      LOG(LS_WARNING) << "Ignoring operation for illegal value " << value;
+      return false;
+    }
+  }
+  return true;
+}
+
+bool OptionsFile::GetStringValue(const std::string& option,
+                                 std::string *out_val) const {
+  LOG(LS_VERBOSE) << "OptionsFile::GetStringValue "
+                  << option;
+  if (!IsLegalName(option)) {
+    return false;
+  }
+  OptionsMap::const_iterator i = options_.find(option);
+  if (i == options_.end()) {
+    return false;
+  }
+  *out_val = i->second;
+  return true;
+}
+
+bool OptionsFile::GetIntValue(const std::string& option,
+                              int *out_val) const {
+  LOG(LS_VERBOSE) << "OptionsFile::GetIntValue "
+                  << option;
+  if (!IsLegalName(option)) {
+    return false;
+  }
+  OptionsMap::const_iterator i = options_.find(option);
+  if (i == options_.end()) {
+    return false;
+  }
+  return FromString(i->second, out_val);
+}
+
+bool OptionsFile::SetStringValue(const std::string& option,
+                                 const std::string& value) {
+  LOG(LS_VERBOSE) << "OptionsFile::SetStringValue "
+                  << option << ":" << value;
+  if (!IsLegalName(option) || !IsLegalValue(value)) {
+    return false;
+  }
+  options_[option] = value;
+  return true;
+}
+
+bool OptionsFile::SetIntValue(const std::string& option,
+                              int value) {
+  LOG(LS_VERBOSE) << "OptionsFile::SetIntValue "
+                  << option << ":" << value;
+  if (!IsLegalName(option)) {
+    return false;
+  }
+  return ToString(value, &options_[option]);
+}
+
+bool OptionsFile::RemoveValue(const std::string& option) {
+  LOG(LS_VERBOSE) << "OptionsFile::RemoveValue " << option;
+  if (!IsLegalName(option)) {
+    return false;
+  }
+  options_.erase(option);
+  return true;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/optionsfile.h b/talk/base/optionsfile.h
new file mode 100644
index 0000000..9e5f457
--- /dev/null
+++ b/talk/base/optionsfile.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_OPTIONSFILE_H_
+#define TALK_BASE_OPTIONSFILE_H_
+
+#include <map>
+#include <string>
+
+namespace talk_base {
+
+// Implements storage of simple options in a text file on disk. This is
+// cross-platform, but it is intended mostly for Linux where there is no
+// first-class options storage system.
+class OptionsFile {
+ public:
+  OptionsFile(const std::string &path);
+
+  // Loads the file from disk, overwriting the in-memory values.
+  bool Load();
+  // Saves the contents in memory, overwriting the on-disk values.
+  bool Save();
+
+  bool GetStringValue(const std::string& option, std::string* out_val) const;
+  bool GetIntValue(const std::string& option, int* out_val) const;
+  bool SetStringValue(const std::string& option, const std::string& val);
+  bool SetIntValue(const std::string& option, int val);
+  bool RemoveValue(const std::string& option);
+
+ private:
+  typedef std::map<std::string, std::string> OptionsMap;
+
+  static bool IsLegalName(const std::string &name);
+  static bool IsLegalValue(const std::string &value);
+
+  std::string path_;
+  OptionsMap options_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_OPTIONSFILE_H_
diff --git a/talk/base/optionsfile_unittest.cc b/talk/base/optionsfile_unittest.cc
new file mode 100644
index 0000000..65861ff
--- /dev/null
+++ b/talk/base/optionsfile_unittest.cc
@@ -0,0 +1,178 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/optionsfile.h"
+
+namespace talk_base {
+
+#ifdef ANDROID
+static const char *kTestFile = "/sdcard/.testfile";
+#elif CHROMEOS
+static const char *kTestFile = "/tmp/.testfile";
+#else
+static const char *kTestFile = ".testfile";
+#endif
+
+static const std::string kTestOptionA = "test-option-a";
+static const std::string kTestOptionB = "test-option-b";
+static const std::string kTestString1 = "a string";
+static const std::string kTestString2 = "different string";
+static const std::string kOptionWithEquals = "foo=bar";
+static const std::string kOptionWithNewline = "foo\nbar";
+static const std::string kValueWithEquals = "baz=quux";
+static const std::string kValueWithNewline = "baz\nquux";
+static const std::string kEmptyString = "";
+static const char kOptionWithUtf8[] = {'O', 'p', 't', '\302', '\256', 'i', 'o',
+    'n', '\342', '\204', '\242', '\0'};  // Opt(R)io(TM).
+static const char kValueWithUtf8[] = {'V', 'a', 'l', '\302', '\256', 'v', 'e',
+    '\342', '\204', '\242', '\0'};  // Val(R)ue(TM).
+static int kTestInt1 = 12345;
+static int kTestInt2 = 67890;
+static int kNegInt = -634;
+static int kZero = 0;
+
+TEST(OptionsFile, GetSetString) {
+  OptionsFile store(kTestFile);
+  // Clear contents of the file on disk.
+  EXPECT_TRUE(store.Save());
+  std::string out1, out2;
+  EXPECT_FALSE(store.GetStringValue(kTestOptionA, &out1));
+  EXPECT_FALSE(store.GetStringValue(kTestOptionB, &out2));
+  EXPECT_TRUE(store.SetStringValue(kTestOptionA, kTestString1));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_TRUE(store.SetStringValue(kTestOptionB, kTestString2));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out1));
+  EXPECT_TRUE(store.GetStringValue(kTestOptionB, &out2));
+  EXPECT_EQ(kTestString1, out1);
+  EXPECT_EQ(kTestString2, out2);
+  EXPECT_TRUE(store.RemoveValue(kTestOptionA));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_TRUE(store.RemoveValue(kTestOptionB));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_FALSE(store.GetStringValue(kTestOptionA, &out1));
+  EXPECT_FALSE(store.GetStringValue(kTestOptionB, &out2));
+}
+
+TEST(OptionsFile, GetSetInt) {
+  OptionsFile store(kTestFile);
+  // Clear contents of the file on disk.
+  EXPECT_TRUE(store.Save());
+  int out1, out2;
+  EXPECT_FALSE(store.GetIntValue(kTestOptionA, &out1));
+  EXPECT_FALSE(store.GetIntValue(kTestOptionB, &out2));
+  EXPECT_TRUE(store.SetIntValue(kTestOptionA, kTestInt1));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_TRUE(store.SetIntValue(kTestOptionB, kTestInt2));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_TRUE(store.GetIntValue(kTestOptionA, &out1));
+  EXPECT_TRUE(store.GetIntValue(kTestOptionB, &out2));
+  EXPECT_EQ(kTestInt1, out1);
+  EXPECT_EQ(kTestInt2, out2);
+  EXPECT_TRUE(store.RemoveValue(kTestOptionA));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_TRUE(store.RemoveValue(kTestOptionB));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_FALSE(store.GetIntValue(kTestOptionA, &out1));
+  EXPECT_FALSE(store.GetIntValue(kTestOptionB, &out2));
+  EXPECT_TRUE(store.SetIntValue(kTestOptionA, kNegInt));
+  EXPECT_TRUE(store.GetIntValue(kTestOptionA, &out1));
+  EXPECT_EQ(kNegInt, out1);
+  EXPECT_TRUE(store.SetIntValue(kTestOptionA, kZero));
+  EXPECT_TRUE(store.GetIntValue(kTestOptionA, &out1));
+  EXPECT_EQ(kZero, out1);
+}
+
+TEST(OptionsFile, Persist) {
+  {
+    OptionsFile store(kTestFile);
+    // Clear contents of the file on disk.
+    EXPECT_TRUE(store.Save());
+    EXPECT_TRUE(store.SetStringValue(kTestOptionA, kTestString1));
+    EXPECT_TRUE(store.SetIntValue(kTestOptionB, kNegInt));
+    EXPECT_TRUE(store.Save());
+  }
+  {
+    OptionsFile store(kTestFile);
+    // Load the saved contents from above.
+    EXPECT_TRUE(store.Load());
+    std::string out1;
+    int out2;
+    EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out1));
+    EXPECT_TRUE(store.GetIntValue(kTestOptionB, &out2));
+    EXPECT_EQ(kTestString1, out1);
+    EXPECT_EQ(kNegInt, out2);
+  }
+}
+
+TEST(OptionsFile, SpecialCharacters) {
+  OptionsFile store(kTestFile);
+  // Clear contents of the file on disk.
+  EXPECT_TRUE(store.Save());
+  std::string out;
+  EXPECT_FALSE(store.SetStringValue(kOptionWithEquals, kTestString1));
+  EXPECT_FALSE(store.GetStringValue(kOptionWithEquals, &out));
+  EXPECT_FALSE(store.SetStringValue(kOptionWithNewline, kTestString1));
+  EXPECT_FALSE(store.GetStringValue(kOptionWithNewline, &out));
+  EXPECT_TRUE(store.SetStringValue(kOptionWithUtf8, kValueWithUtf8));
+  EXPECT_TRUE(store.SetStringValue(kTestOptionA, kTestString1));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out));
+  EXPECT_EQ(kTestString1, out);
+  EXPECT_TRUE(store.GetStringValue(kOptionWithUtf8, &out));
+  EXPECT_EQ(kValueWithUtf8, out);
+  EXPECT_FALSE(store.SetStringValue(kTestOptionA, kValueWithNewline));
+  EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out));
+  EXPECT_EQ(kTestString1, out);
+  EXPECT_TRUE(store.SetStringValue(kTestOptionA, kValueWithEquals));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out));
+  EXPECT_EQ(kValueWithEquals, out);
+  EXPECT_TRUE(store.SetStringValue(kEmptyString, kTestString2));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_TRUE(store.GetStringValue(kEmptyString, &out));
+  EXPECT_EQ(kTestString2, out);
+  EXPECT_TRUE(store.SetStringValue(kTestOptionB, kEmptyString));
+  EXPECT_TRUE(store.Save());
+  EXPECT_TRUE(store.Load());
+  EXPECT_TRUE(store.GetStringValue(kTestOptionB, &out));
+  EXPECT_EQ(kEmptyString, out);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/pathutils_unittest.cc b/talk/base/pathutils_unittest.cc
new file mode 100644
index 0000000..0a9739b
--- /dev/null
+++ b/talk/base/pathutils_unittest.cc
@@ -0,0 +1,65 @@
+/*
+ * libjingle
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/pathutils.h"
+#include "talk/base/gunit.h"
+
+TEST(Pathname, ReturnsDotForEmptyPathname) {
+  const std::string kCWD =
+      std::string(".") + talk_base::Pathname::DefaultFolderDelimiter();
+
+  talk_base::Pathname path("/", "");
+  EXPECT_FALSE(path.empty());
+  EXPECT_FALSE(path.folder().empty());
+  EXPECT_TRUE (path.filename().empty());
+  EXPECT_FALSE(path.pathname().empty());
+  EXPECT_EQ(std::string("/"), path.pathname());
+
+  path.SetPathname("", "foo");
+  EXPECT_FALSE(path.empty());
+  EXPECT_TRUE (path.folder().empty());
+  EXPECT_FALSE(path.filename().empty());
+  EXPECT_FALSE(path.pathname().empty());
+  EXPECT_EQ(std::string("foo"), path.pathname());
+
+  path.SetPathname("", "");
+  EXPECT_TRUE (path.empty());
+  EXPECT_TRUE (path.folder().empty());
+  EXPECT_TRUE (path.filename().empty());
+  EXPECT_FALSE(path.pathname().empty());
+  EXPECT_EQ(kCWD, path.pathname());
+
+  path.SetPathname(kCWD, "");
+  EXPECT_FALSE(path.empty());
+  EXPECT_FALSE(path.folder().empty());
+  EXPECT_TRUE (path.filename().empty());
+  EXPECT_FALSE(path.pathname().empty());
+  EXPECT_EQ(kCWD, path.pathname());
+
+  talk_base::Pathname path2("c:/foo bar.txt");
+  EXPECT_EQ(path2.url(), std::string("file:///c:/foo%20bar.txt"));
+}
diff --git a/talk/base/physicalsocketserver.cc b/talk/base/physicalsocketserver.cc
index 9cb5f4f..59eb550 100644
--- a/talk/base/physicalsocketserver.cc
+++ b/talk/base/physicalsocketserver.cc
@@ -57,7 +57,7 @@
 #include "talk/base/logging.h"
 #include "talk/base/nethelpers.h"
 #include "talk/base/physicalsocketserver.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 #include "talk/base/winping.h"
 #include "talk/base/win32socketinit.h"
 
@@ -591,7 +591,13 @@
   // for 128.
   static const int kNumPosixSignals = 128;
 
-  static PosixSignalHandler *Instance() { return &instance_; }
+  // There is just a single global instance. (Signal handlers do not get any
+  // sort of user-defined void * parameter, so they can't access anything that
+  // isn't global.)
+  static PosixSignalHandler* Instance() {
+    LIBJINGLE_DEFINE_STATIC_LOCAL(PosixSignalHandler, instance, ());
+    return &instance;
+  }
 
   // Returns true if the given signal number is set.
   bool IsSignalSet(int signum) const {
@@ -674,11 +680,6 @@
     close(fd2);
   }
 
-  // There is just a single global instance. (Signal handlers do not get any
-  // sort of user-defined void * parameter, so they can't access anything that
-  // isn't global.)
-  static PosixSignalHandler instance_;
-
   int afd_[2];
   // These are boolean flags that will be set in our signal handler and read
   // and cleared from Wait(). There is a race involved in this, but it is
@@ -693,8 +694,6 @@
   volatile uint8 received_signal_[kNumPosixSignals];
 };
 
-PosixSignalHandler PosixSignalHandler::instance_;
-
 class PosixSignalDispatcher : public Dispatcher {
  public:
   PosixSignalDispatcher(PhysicalSocketServer *owner) : owner_(owner) {
@@ -1409,6 +1408,10 @@
   return true;
 }
 
+Dispatcher* PhysicalSocketServer::signal_dispatcher() {
+  return signal_dispatcher_.get();
+}
+
 bool PhysicalSocketServer::InstallSignal(int signum, void (*handler)(int)) {
   struct sigaction act;
   // It doesn't really matter what we set this mask to.
diff --git a/talk/base/physicalsocketserver.h b/talk/base/physicalsocketserver.h
index aeb8348..1b7a298 100644
--- a/talk/base/physicalsocketserver.h
+++ b/talk/base/physicalsocketserver.h
@@ -2,26 +2,26 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
@@ -73,7 +73,7 @@
 
 // A socket server that provides the real sockets of the underlying OS.
 class PhysicalSocketServer : public SocketServer {
-public:
+ public:
   PhysicalSocketServer();
   virtual ~PhysicalSocketServer();
 
@@ -104,10 +104,13 @@
   // Dispatching signals on multiple PhysicalSocketServers is not reliable.
   // The signal mask is not modified. It is the caller's responsibily to
   // maintain it as desired.
-  bool SetPosixSignalHandler(int signum, void (*handler)(int));
+  virtual bool SetPosixSignalHandler(int signum, void (*handler)(int));
+
+ protected:
+  Dispatcher* signal_dispatcher();
 #endif
 
-private:
+ private:
   typedef std::vector<Dispatcher*> DispatcherList;
   typedef std::vector<size_t*> IteratorList;
 
diff --git a/talk/base/physicalsocketserver_unittest.cc b/talk/base/physicalsocketserver_unittest.cc
new file mode 100644
index 0000000..fa51e55
--- /dev/null
+++ b/talk/base/physicalsocketserver_unittest.cc
@@ -0,0 +1,224 @@
+/*
+ * 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 <signal.h>
+#include <stdarg.h>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socket_unittest.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+class PhysicalSocketTest : public SocketTest {
+};
+
+TEST_F(PhysicalSocketTest, TestConnect) {
+  SocketTest::TestConnect();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectWithDnsLookup) {
+  SocketTest::TestConnectWithDnsLookup();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectFail) {
+  SocketTest::TestConnectFail();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFail) {
+  SocketTest::TestConnectWithDnsLookupFail();
+}
+
+#ifdef OSX
+// This test crashes the OS X kernel on 10.6 (at bsd/netinet/tcp_subr.c:2118).
+TEST_F(PhysicalSocketTest, DISABLED_TestConnectWithClosedSocket) {
+#else
+TEST_F(PhysicalSocketTest, TestConnectWithClosedSocket) {
+#endif
+  SocketTest::TestConnectWithClosedSocket();
+}
+
+TEST_F(PhysicalSocketTest, TestServerCloseDuringConnect) {
+  SocketTest::TestServerCloseDuringConnect();
+}
+
+TEST_F(PhysicalSocketTest, TestClientCloseDuringConnect) {
+  SocketTest::TestClientCloseDuringConnect();
+}
+
+TEST_F(PhysicalSocketTest, TestServerClose) {
+  SocketTest::TestServerClose();
+}
+
+TEST_F(PhysicalSocketTest, TestCloseInClosedCallback) {
+  SocketTest::TestCloseInClosedCallback();
+}
+
+TEST_F(PhysicalSocketTest, TestSocketServerWait) {
+  SocketTest::TestSocketServerWait();
+}
+
+TEST_F(PhysicalSocketTest, TestTcp) {
+  SocketTest::TestTcp();
+}
+
+TEST_F(PhysicalSocketTest, TestUdp) {
+  SocketTest::TestUdp();
+}
+
+TEST_F(PhysicalSocketTest, TestGetSetOptions) {
+  SocketTest::TestGetSetOptions();
+}
+
+#ifdef POSIX
+
+class PosixSignalDeliveryTest : public testing::Test {
+ public:
+  static void RecordSignal(int signum) {
+    signals_received_.push_back(signum);
+    signaled_thread_ = Thread::Current();
+  }
+
+ protected:
+  void SetUp() {
+    ss_.reset(new PhysicalSocketServer());
+  }
+
+  void TearDown() {
+    ss_.reset(NULL);
+    signals_received_.clear();
+    signaled_thread_ = NULL;
+  }
+
+  bool ExpectSignal(int signum) {
+    if (signals_received_.empty()) {
+      LOG(LS_ERROR) << "ExpectSignal(): No signal received";
+      return false;
+    }
+    if (signals_received_[0] != signum) {
+      LOG(LS_ERROR) << "ExpectSignal(): Received signal " <<
+          signals_received_[0] << ", expected " << signum;
+      return false;
+    }
+    signals_received_.erase(signals_received_.begin());
+    return true;
+  }
+
+  bool ExpectNone() {
+    bool ret = signals_received_.empty();
+    if (!ret) {
+      LOG(LS_ERROR) << "ExpectNone(): Received signal " << signals_received_[0]
+          << ", expected none";
+    }
+    return ret;
+  }
+
+  static std::vector<int> signals_received_;
+  static Thread *signaled_thread_;
+
+  scoped_ptr<PhysicalSocketServer> ss_;
+};
+
+std::vector<int> PosixSignalDeliveryTest::signals_received_;
+Thread *PosixSignalDeliveryTest::signaled_thread_ = NULL;
+
+// Test receiving a synchronous signal while not in Wait() and then entering
+// Wait() afterwards.
+TEST_F(PosixSignalDeliveryTest, RaiseThenWait) {
+  ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal);
+  raise(SIGTERM);
+  EXPECT_TRUE(ss_->Wait(0, true));
+  EXPECT_TRUE(ExpectSignal(SIGTERM));
+  EXPECT_TRUE(ExpectNone());
+}
+
+// Test that we can handle getting tons of repeated signals and that we see all
+// the different ones.
+TEST_F(PosixSignalDeliveryTest, InsanelyManySignals) {
+  ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal);
+  ss_->SetPosixSignalHandler(SIGINT, &RecordSignal);
+  for (int i = 0; i < 10000; ++i) {
+    raise(SIGTERM);
+  }
+  raise(SIGINT);
+  EXPECT_TRUE(ss_->Wait(0, true));
+  // Order will be lowest signal numbers first.
+  EXPECT_TRUE(ExpectSignal(SIGINT));
+  EXPECT_TRUE(ExpectSignal(SIGTERM));
+  EXPECT_TRUE(ExpectNone());
+}
+
+// Test that a signal during a Wait() call is detected.
+TEST_F(PosixSignalDeliveryTest, SignalDuringWait) {
+  ss_->SetPosixSignalHandler(SIGALRM, &RecordSignal);
+  alarm(1);
+  EXPECT_TRUE(ss_->Wait(1500, true));
+  EXPECT_TRUE(ExpectSignal(SIGALRM));
+  EXPECT_TRUE(ExpectNone());
+}
+
+class RaiseSigTermRunnable : public Runnable {
+  void Run(Thread *thread) {
+    thread->socketserver()->Wait(1000, false);
+
+    // Allow SIGTERM. This will be the only thread with it not masked so it will
+    // be delivered to us.
+    sigset_t mask;
+    sigemptyset(&mask);
+    pthread_sigmask(SIG_SETMASK, &mask, NULL);
+
+    // Raise it.
+    raise(SIGTERM);
+  }
+};
+
+// Test that it works no matter what thread the kernel chooses to give the
+// signal to (since it's not guaranteed to be the one that Wait() runs on).
+TEST_F(PosixSignalDeliveryTest, SignalOnDifferentThread) {
+  ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal);
+  // Mask out SIGTERM so that it can't be delivered to this thread.
+  sigset_t mask;
+  sigemptyset(&mask);
+  sigaddset(&mask, SIGTERM);
+  EXPECT_EQ(0, pthread_sigmask(SIG_SETMASK, &mask, NULL));
+  // Start a new thread that raises it. It will have to be delivered to that
+  // thread. Our implementation should safely handle it and dispatch
+  // RecordSignal() on this thread.
+  scoped_ptr<Thread> thread(new Thread());
+  thread->Start(new RaiseSigTermRunnable());
+  EXPECT_TRUE(ss_->Wait(1500, true));
+  EXPECT_TRUE(ExpectSignal(SIGTERM));
+  EXPECT_EQ(Thread::Current(), signaled_thread_);
+  EXPECT_TRUE(ExpectNone());
+}
+
+#endif
+
+}  // namespace talk_base
diff --git a/talk/base/posix.cc b/talk/base/posix.cc
new file mode 100644
index 0000000..3502f6d
--- /dev/null
+++ b/talk/base/posix.cc
@@ -0,0 +1,148 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/posix.h"
+
+#include <sys/wait.h>
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef LINUX
+#include "talk/base/linuxfdwalk.h"
+#endif
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+#ifdef LINUX
+static void closefds(void *close_errors, int fd) {
+  if (fd <= 2) {
+    // We leave stdin/out/err open to the browser's terminal, if any.
+    return;
+  }
+  if (close(fd) < 0) {
+    *static_cast<bool *>(close_errors) = true;
+  }
+}
+#endif
+
+enum {
+  EXIT_FLAG_CHDIR_ERRORS       = 1 << 0,
+#ifdef LINUX
+  EXIT_FLAG_FDWALK_ERRORS      = 1 << 1,
+  EXIT_FLAG_CLOSE_ERRORS       = 1 << 2,
+#endif
+  EXIT_FLAG_SECOND_FORK_FAILED = 1 << 3,
+};
+
+bool RunAsDaemon(const char *file, const char *const argv[]) {
+  // Fork intermediate child to daemonize.
+  pid_t pid = fork();
+  if (pid < 0) {
+    LOG_ERR(LS_ERROR) << "fork()";
+    return false;
+  } else if (!pid) {
+    // Child.
+
+    // We try to close all fds and change directory to /, but if that fails we
+    // keep going because it's not critical.
+    int exit_code = 0;
+    if (chdir("/") < 0) {
+      exit_code |= EXIT_FLAG_CHDIR_ERRORS;
+    }
+#ifdef LINUX
+    bool close_errors = false;
+    if (fdwalk(&closefds, &close_errors) < 0) {
+      exit_code |= EXIT_FLAG_FDWALK_ERRORS;
+    }
+    if (close_errors) {
+      exit_code |= EXIT_FLAG_CLOSE_ERRORS;
+    }
+#endif
+
+    // Fork again to become a daemon.
+    pid = fork();
+    // It is important that everything here use _exit() and not exit(), because
+    // exit() would call the destructors of all global variables in the whole
+    // process, which is both unnecessary and unsafe.
+    if (pid < 0) {
+      exit_code |= EXIT_FLAG_SECOND_FORK_FAILED;
+      _exit(exit_code);  // if second fork failed
+    } else if (!pid) {
+      // Child.
+      // Successfully daemonized. Run command.
+      // POSIX requires the args to be typed as non-const for historical
+      // reasons, but it mandates that the actual implementation be const, so
+      // the cast is safe.
+      execvp(file, const_cast<char *const *>(argv));
+      _exit(255);  // if execvp failed
+    }
+
+    // Parent.
+    // Successfully spawned process, but report any problems to the parent where
+    // we can log them.
+    _exit(exit_code);
+  }
+
+  // Parent. Reap intermediate child.
+  int status;
+  pid_t child = waitpid(pid, &status, 0);
+  if (child < 0) {
+    LOG_ERR(LS_ERROR) << "Error in waitpid()";
+    return false;
+  }
+  if (child != pid) {
+    // Should never happen (see man page).
+    LOG(LS_ERROR) << "waitpid() chose wrong child???";
+    return false;
+  }
+  if (!WIFEXITED(status)) {
+    LOG(LS_ERROR) << "Intermediate child killed uncleanly";  // Probably crashed
+    return false;
+  }
+
+  int exit_code = WEXITSTATUS(status);
+  if (exit_code & EXIT_FLAG_CHDIR_ERRORS) {
+    LOG(LS_WARNING) << "Child reported probles calling chdir()";
+  }
+#ifdef LINUX
+  if (exit_code & EXIT_FLAG_FDWALK_ERRORS) {
+    LOG(LS_WARNING) << "Child reported problems calling fdwalk()";
+  }
+  if (exit_code & EXIT_FLAG_CLOSE_ERRORS) {
+    LOG(LS_WARNING) << "Child reported problems calling close()";
+  }
+#endif
+  if (exit_code & EXIT_FLAG_SECOND_FORK_FAILED) {
+    LOG(LS_ERROR) << "Failed to daemonize";
+    // This means the command was not launched, so failure.
+    return false;
+  }
+  return true;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/posix.h b/talk/base/posix.h
new file mode 100644
index 0000000..15c2c90
--- /dev/null
+++ b/talk/base/posix.h
@@ -0,0 +1,42 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_POSIX_H_
+#define TALK_BASE_POSIX_H_
+
+namespace talk_base {
+
+// Runs the given executable name as a daemon, so that it executes concurrently
+// with this process. Upon completion, the daemon process will automatically be
+// reaped by init(8), so an error exit status or a failure to start the
+// executable are not reported. Returns true if the daemon process was forked
+// successfully, else false.
+bool RunAsDaemon(const char *file, const char *const argv[]);
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_POSIX_H_
diff --git a/talk/base/proxy_unittest.cc b/talk/base/proxy_unittest.cc
new file mode 100644
index 0000000..e1a51f3
--- /dev/null
+++ b/talk/base/proxy_unittest.cc
@@ -0,0 +1,149 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "talk/base/autodetectproxy.h"
+#include "talk/base/gunit.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/proxyserver.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/testclient.h"
+#include "talk/base/testechoserver.h"
+#include "talk/base/virtualsocketserver.h"
+
+using talk_base::Socket;
+using talk_base::Thread;
+using talk_base::SocketAddress;
+
+static const SocketAddress kSocksProxyIntAddr("1.2.3.4", 1080);
+static const SocketAddress kSocksProxyExtAddr("1.2.3.5", 0);
+static const SocketAddress kHttpsProxyIntAddr("1.2.3.4", 443);
+static const SocketAddress kHttpsProxyExtAddr("1.2.3.5", 0);
+static const SocketAddress kBogusProxyIntAddr("1.2.3.4", 999);
+
+// Used to run a proxy detect on the current thread. Otherwise we would need
+// to make both threads share the same VirtualSocketServer.
+class AutoDetectProxyRunner : public talk_base::AutoDetectProxy {
+ public:
+  explicit AutoDetectProxyRunner(const std::string& agent)
+      : AutoDetectProxy(agent) {}
+  void Run() {
+    DoWork();
+    Thread::Current()->Restart();  // needed to reset the messagequeue
+  }
+};
+
+// Sets up a virtual socket server and HTTPS/SOCKS5 proxy servers.
+class ProxyTest : public testing::Test {
+ public:
+  ProxyTest() : ss_(new talk_base::VirtualSocketServer(NULL)) {
+    Thread::Current()->set_socketserver(ss_.get());
+    socks_.reset(new talk_base::SocksProxyServer(
+        ss_.get(), kSocksProxyIntAddr, ss_.get(), kSocksProxyExtAddr));
+    https_.reset(new talk_base::HttpListenServer());
+    https_->Listen(kHttpsProxyIntAddr);
+  }
+  ~ProxyTest() {
+    Thread::Current()->set_socketserver(NULL);
+  }
+
+  talk_base::SocketServer* ss() { return ss_.get(); }
+
+  talk_base::ProxyType DetectProxyType(const SocketAddress& address) {
+    talk_base::ProxyType type;
+    AutoDetectProxyRunner* detect = new AutoDetectProxyRunner("unittest/1.0");
+    detect->set_proxy(address);
+    detect->Run();  // blocks until done
+    type = detect->proxy().type;
+    detect->Destroy(false);
+    return type;
+  }
+
+ private:
+  talk_base::scoped_ptr<talk_base::SocketServer> ss_;
+  talk_base::scoped_ptr<talk_base::SocksProxyServer> socks_;
+  // TODO: Make this a real HTTPS proxy server.
+  talk_base::scoped_ptr<talk_base::HttpListenServer> https_;
+};
+
+// Tests whether we can use a SOCKS5 proxy to connect to a server.
+TEST_F(ProxyTest, TestSocks5Connect) {
+  talk_base::AsyncSocket* socket = ss()->CreateAsyncSocket(SOCK_STREAM);
+  talk_base::AsyncSocksProxySocket* proxy_socket =
+      new talk_base::AsyncSocksProxySocket(socket, kSocksProxyIntAddr,
+                                           "", talk_base::CryptString());
+
+  talk_base::TestEchoServer server(Thread::Current(), SocketAddress());
+
+  talk_base::AsyncTCPSocket* packet_socket = talk_base::AsyncTCPSocket::Create(
+      proxy_socket, SocketAddress(), server.address());
+  EXPECT_TRUE(packet_socket != NULL);
+  talk_base::TestClient client(packet_socket);
+
+  EXPECT_EQ(Socket::CS_CONNECTING, proxy_socket->GetState());
+  EXPECT_TRUE(client.CheckConnected());
+  EXPECT_EQ(Socket::CS_CONNECTED, proxy_socket->GetState());
+  EXPECT_EQ(server.address(), client.remote_address());
+  client.Send("foo", 3);
+  EXPECT_TRUE(client.CheckNextPacket("foo", 3, NULL));
+  EXPECT_TRUE(client.CheckNoPacket());
+}
+
+/*
+// Tests whether we can use a HTTPS proxy to connect to a server.
+TEST_F(ProxyTest, TestHttpsConnect) {
+  AsyncSocket* socket = ss()->CreateAsyncSocket(SOCK_STREAM);
+  AsyncHttpsProxySocket* proxy_socket = new AsyncHttpsProxySocket(
+      socket, "unittest/1.0", kHttpsProxyIntAddress, "", CryptString());
+  TestClient client(new AsyncTCPSocket(proxy_socket));
+  TestEchoServer server(Thread::Current(), SocketAddress());
+
+  EXPECT_TRUE(client.Connect(server.address()));
+  EXPECT_TRUE(client.CheckConnected());
+  EXPECT_EQ(server.address(), client.remote_address());
+  client.Send("foo", 3);
+  EXPECT_TRUE(client.CheckNextPacket("foo", 3, NULL));
+  EXPECT_TRUE(client.CheckNoPacket());
+}
+*/
+
+// Tests whether we can autodetect a SOCKS5 proxy.
+TEST_F(ProxyTest, TestAutoDetectSocks5) {
+  EXPECT_EQ(talk_base::PROXY_SOCKS5, DetectProxyType(kSocksProxyIntAddr));
+}
+
+/*
+// Tests whether we can autodetect a HTTPS proxy.
+TEST_F(ProxyTest, TestAutoDetectHttps) {
+  EXPECT_EQ(talk_base::PROXY_HTTPS, DetectProxyType(kHttpsProxyIntAddr));
+}
+*/
+
+// Tests whether we fail properly for no proxy.
+TEST_F(ProxyTest, TestAutoDetectBogus) {
+  EXPECT_EQ(talk_base::PROXY_UNKNOWN, DetectProxyType(kBogusProxyIntAddr));
+}
diff --git a/talk/base/proxydetect.cc b/talk/base/proxydetect.cc
index 66213ee..4bfae29 100644
--- a/talk/base/proxydetect.cc
+++ b/talk/base/proxydetect.cc
@@ -233,7 +233,9 @@
       m = 0;
     uint32 mask = (m == 0) ? 0 : (~0UL) << (32 - m);
     SocketAddress addr(url.host(), 0);
-    return !addr.IsUnresolved() && ((addr.ip() & mask) == (ip & mask));
+    // TODO: Support IPv6 proxyitems. This code block is IPv4 only anyway.
+    return !addr.IsUnresolved() &&
+        ((addr.ipaddr().v4AddressAsHostOrderInteger() & mask) == (ip & mask));
   }
 
   // .foo.com
diff --git a/talk/base/proxydetect_unittest.cc b/talk/base/proxydetect_unittest.cc
new file mode 100644
index 0000000..4855650
--- /dev/null
+++ b/talk/base/proxydetect_unittest.cc
@@ -0,0 +1,182 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/fileutils_mock.h"
+#include "talk/base/proxydetect.h"
+
+namespace talk_base {
+
+static const std::string kFirefoxProfilesIni =
+  "[Profile0]\n"
+  "Name=default\n"
+  "IsRelative=1\n"
+  "Path=Profiles/2de53ejb.default\n"
+  "Default=1\n";
+
+static const std::string kFirefoxHeader =
+  "# Mozilla User Preferences\n"
+  "\n"
+  "/* Some Comments\n"
+  "*\n"
+  "*/\n"
+  "\n";
+
+static const std::string kFirefoxCorruptHeader =
+  "iuahueqe32164";
+
+static const std::string kProxyAddress = "proxy.net.com";
+static const int kProxyPort = 9999;
+
+// Mocking out platform specific path to firefox prefs file.
+class FirefoxPrefsFileSystem : public FakeFileSystem {
+ public:
+  explicit FirefoxPrefsFileSystem(const std::vector<File>& all_files) :
+      FakeFileSystem(all_files) {
+  }
+  virtual FileStream* OpenFile(const Pathname& filename,
+                               const std::string& mode) {
+    // TODO: We could have a platform dependent check of paths here.
+    std::string name = filename.basename();
+    name.append(filename.extension());
+    EXPECT_TRUE(name.compare("prefs.js") == 0 ||
+                name.compare("profiles.ini") == 0);
+    FileStream* stream = FakeFileSystem::OpenFile(name, mode);
+    return stream;
+  }
+};
+
+class ProxyDetectTest : public testing::Test {
+};
+
+bool GetProxyInfo(const std::string prefs, ProxyInfo* info) {
+  std::vector<talk_base::FakeFileSystem::File> files;
+  files.push_back(talk_base::FakeFileSystem::File("profiles.ini",
+                                                  kFirefoxProfilesIni));
+  files.push_back(talk_base::FakeFileSystem::File("prefs.js", prefs));
+  talk_base::FilesystemScope fs(new talk_base::FirefoxPrefsFileSystem(files));
+  return GetProxySettingsForUrl("Firefox", "www.google.com", *info, false);
+}
+
+// Verifies that an empty Firefox prefs file results in no proxy detected.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxEmptyPrefs) {
+  ProxyInfo proxy_info;
+  EXPECT_TRUE(GetProxyInfo(kFirefoxHeader, &proxy_info));
+  EXPECT_EQ(PROXY_NONE, proxy_info.type);
+}
+
+// Verifies that corrupted prefs file results in no proxy detected.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxCorruptedPrefs) {
+  ProxyInfo proxy_info;
+  EXPECT_TRUE(GetProxyInfo(kFirefoxCorruptHeader, &proxy_info));
+  EXPECT_EQ(PROXY_NONE, proxy_info.type);
+}
+
+// Verifies that SOCKS5 proxy is detected if configured. SOCKS uses a
+// handshake protocol to inform the proxy software about the
+// connection that the client is trying to make and may be used for
+// any form of TCP or UDP socket connection.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxySocks) {
+  ProxyInfo proxy_info;
+  SocketAddress proxy_address("proxy.socks.com", 6666);
+  std::string prefs(kFirefoxHeader);
+  prefs.append("user_pref(\"network.proxy.socks\", \"proxy.socks.com\");\n");
+  prefs.append("user_pref(\"network.proxy.socks_port\", 6666);\n");
+  prefs.append("user_pref(\"network.proxy.type\", 1);\n");
+
+  EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info));
+
+  EXPECT_EQ(PROXY_SOCKS5, proxy_info.type);
+  EXPECT_EQ(proxy_address, proxy_info.address);
+}
+
+// Verified that SSL proxy is detected if configured. SSL proxy is an
+// extention of a HTTP proxy to support secure connections.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxySsl) {
+  ProxyInfo proxy_info;
+  SocketAddress proxy_address("proxy.ssl.com", 7777);
+  std::string prefs(kFirefoxHeader);
+
+  prefs.append("user_pref(\"network.proxy.ssl\", \"proxy.ssl.com\");\n");
+  prefs.append("user_pref(\"network.proxy.ssl_port\", 7777);\n");
+  prefs.append("user_pref(\"network.proxy.type\", 1);\n");
+
+  EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info));
+
+  EXPECT_EQ(PROXY_HTTPS, proxy_info.type);
+  EXPECT_EQ(proxy_address, proxy_info.address);
+}
+
+// Verifies that a HTTP proxy is detected if configured.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyHttp) {
+  ProxyInfo proxy_info;
+  SocketAddress proxy_address("proxy.http.com", 8888);
+  std::string prefs(kFirefoxHeader);
+
+  prefs.append("user_pref(\"network.proxy.http\", \"proxy.http.com\");\n");
+  prefs.append("user_pref(\"network.proxy.http_port\", 8888);\n");
+  prefs.append("user_pref(\"network.proxy.type\", 1);\n");
+
+  EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info));
+
+  EXPECT_EQ(PROXY_HTTPS, proxy_info.type);
+  EXPECT_EQ(proxy_address, proxy_info.address);
+}
+
+// Verifies detection of automatic proxy detection.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyAuto) {
+  ProxyInfo proxy_info;
+  std::string prefs(kFirefoxHeader);
+
+  prefs.append("user_pref(\"network.proxy.type\", 4);\n");
+
+  EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info));
+
+  EXPECT_EQ(PROXY_NONE, proxy_info.type);
+  EXPECT_TRUE(proxy_info.autodetect);
+  EXPECT_TRUE(proxy_info.autoconfig_url.empty());
+}
+
+// Verifies detection of automatic proxy detection using a static url
+// to config file.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyAutoUrl) {
+  ProxyInfo proxy_info;
+  std::string prefs(kFirefoxHeader);
+
+  prefs.append(
+      "user_pref(\"network.proxy.autoconfig_url\", \"http://a/b.pac\");\n");
+  prefs.append("user_pref(\"network.proxy.type\", 2);\n");
+
+  EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info));
+
+  EXPECT_FALSE(proxy_info.autodetect);
+  EXPECT_EQ(PROXY_NONE, proxy_info.type);
+  EXPECT_EQ(0, proxy_info.autoconfig_url.compare("http://a/b.pac"));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/proxyserver.cc b/talk/base/proxyserver.cc
new file mode 100644
index 0000000..d764fd7
--- /dev/null
+++ b/talk/base/proxyserver.cc
@@ -0,0 +1,150 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/proxyserver.h"
+
+#include <algorithm>
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+// ProxyServer
+ProxyServer::ProxyServer(
+    SocketFactory* int_factory, const SocketAddress& int_addr,
+    SocketFactory* ext_factory, const SocketAddress& ext_ip)
+    : ext_factory_(ext_factory), ext_ip_(ext_ip.ipaddr(), 0),  // strip off port
+      server_socket_(int_factory->CreateAsyncSocket(SOCK_STREAM)) {
+  server_socket_->Bind(int_addr);
+  server_socket_->Listen(5);
+  server_socket_->SignalReadEvent.connect(this, &ProxyServer::OnAcceptEvent);
+}
+
+ProxyServer::~ProxyServer() {
+  for (BindingList::iterator it = bindings_.begin();
+       it != bindings_.end(); ++it) {
+    delete (*it);
+  }
+}
+
+void ProxyServer::OnAcceptEvent(AsyncSocket* socket) {
+  ASSERT(socket == server_socket_.get());
+  AsyncSocket* int_socket = socket->Accept(NULL);
+  AsyncProxyServerSocket* wrapped_socket = WrapSocket(int_socket);
+  AsyncSocket* ext_socket = ext_factory_->CreateAsyncSocket(SOCK_STREAM);
+  ext_socket->Bind(ext_ip_);
+  bindings_.push_back(new ProxyBinding(wrapped_socket, ext_socket));
+}
+
+void ProxyServer::OnBindingDestroyed(ProxyBinding* binding) {
+  BindingList::iterator it =
+      std::find(bindings_.begin(), bindings_.end(), binding);
+  delete (*it);
+  bindings_.erase(it);
+}
+
+// ProxyBinding
+ProxyBinding::ProxyBinding(AsyncProxyServerSocket* int_socket,
+                           AsyncSocket* ext_socket)
+    : int_socket_(int_socket), ext_socket_(ext_socket), connected_(false),
+      out_buffer_(kBufferSize), in_buffer_(kBufferSize) {
+  int_socket_->SignalConnectRequest.connect(this,
+                                            &ProxyBinding::OnConnectRequest);
+  int_socket_->SignalReadEvent.connect(this, &ProxyBinding::OnInternalRead);
+  int_socket_->SignalWriteEvent.connect(this, &ProxyBinding::OnInternalWrite);
+  int_socket_->SignalCloseEvent.connect(this, &ProxyBinding::OnInternalClose);
+  ext_socket_->SignalConnectEvent.connect(this,
+                                          &ProxyBinding::OnExternalConnect);
+  ext_socket_->SignalReadEvent.connect(this, &ProxyBinding::OnExternalRead);
+  ext_socket_->SignalWriteEvent.connect(this, &ProxyBinding::OnExternalWrite);
+  ext_socket_->SignalCloseEvent.connect(this, &ProxyBinding::OnExternalClose);
+}
+
+void ProxyBinding::OnConnectRequest(AsyncProxyServerSocket* socket,
+                                   const SocketAddress& addr) {
+  ASSERT(!connected_);
+  ext_socket_->Connect(addr);
+  // TODO: handle errors here
+}
+
+void ProxyBinding::OnInternalRead(AsyncSocket* socket) {
+  Read(int_socket_.get(), &out_buffer_);
+  Write(ext_socket_.get(), &out_buffer_);
+}
+
+void ProxyBinding::OnInternalWrite(AsyncSocket* socket) {
+  Write(int_socket_.get(), &in_buffer_);
+}
+
+void ProxyBinding::OnInternalClose(AsyncSocket* socket, int err) {
+  Destroy();
+}
+
+void ProxyBinding::OnExternalConnect(AsyncSocket* socket) {
+  connected_ = true;
+  int_socket_->SendConnectResult(0, socket->GetRemoteAddress());
+}
+
+void ProxyBinding::OnExternalRead(AsyncSocket* socket) {
+  Read(ext_socket_.get(), &in_buffer_);
+  Write(int_socket_.get(), &in_buffer_);
+}
+
+void ProxyBinding::OnExternalWrite(AsyncSocket* socket) {
+  Write(ext_socket_.get(), &out_buffer_);
+}
+
+void ProxyBinding::OnExternalClose(AsyncSocket* socket, int err) {
+  if (!connected_) {
+    int_socket_->SendConnectResult(err, SocketAddress());
+  }
+  Destroy();
+}
+
+void ProxyBinding::Read(AsyncSocket* socket, FifoBuffer* buffer) {
+  // Only read if the buffer is empty.
+  size_t size;
+  int read;
+  if (buffer->GetBuffered(&size) && size == 0) {
+    void* p = buffer->GetWriteBuffer(&size);
+    read = socket->Recv(p, size);
+    buffer->ConsumeWriteBuffer(_max(read, 0));
+  }
+}
+
+void ProxyBinding::Write(AsyncSocket* socket, FifoBuffer* buffer) {
+  size_t size;
+  int written;
+  const void* p = buffer->GetReadData(&size);
+  written = socket->Send(p, size);
+  buffer->ConsumeReadData(_max(written, 0));
+}
+
+void ProxyBinding::Destroy() {
+  SignalDestroyed(this);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/proxyserver.h b/talk/base/proxyserver.h
new file mode 100644
index 0000000..8e1ab6b
--- /dev/null
+++ b/talk/base/proxyserver.h
@@ -0,0 +1,113 @@
+/*
+ * 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_PROXYSERVER_H_
+#define TALK_BASE_PROXYSERVER_H_
+
+#include <list>
+#include "talk/base/asyncsocket.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+class SocketFactory;
+
+// ProxyServer is a base class that allows for easy construction of proxy
+// servers. With its helper class ProxyBinding, it contains all the necessary
+// logic for receiving and bridging connections. The specific client-server
+// proxy protocol is implemented by an instance of the AsyncProxyServerSocket
+// class; children of ProxyServer implement WrapSocket appropriately to return
+// the correct protocol handler.
+
+class ProxyBinding : public sigslot::has_slots<> {
+ public:
+  ProxyBinding(AsyncProxyServerSocket* in_socket, AsyncSocket* out_socket);
+  sigslot::signal1<ProxyBinding*> SignalDestroyed;
+
+ private:
+  void OnConnectRequest(AsyncProxyServerSocket* socket,
+                        const SocketAddress& addr);
+  void OnInternalRead(AsyncSocket* socket);
+  void OnInternalWrite(AsyncSocket* socket);
+  void OnInternalClose(AsyncSocket* socket, int err);
+  void OnExternalConnect(AsyncSocket* socket);
+  void OnExternalRead(AsyncSocket* socket);
+  void OnExternalWrite(AsyncSocket* socket);
+  void OnExternalClose(AsyncSocket* socket, int err);
+
+  static void Read(AsyncSocket* socket, FifoBuffer* buffer);
+  static void Write(AsyncSocket* socket, FifoBuffer* buffer);
+  void Destroy();
+
+  static const int kBufferSize = 4096;
+  scoped_ptr<AsyncProxyServerSocket> int_socket_;
+  scoped_ptr<AsyncSocket> ext_socket_;
+  bool connected_;
+  FifoBuffer out_buffer_;
+  FifoBuffer in_buffer_;
+  DISALLOW_EVIL_CONSTRUCTORS(ProxyBinding);
+};
+
+class ProxyServer : public sigslot::has_slots<> {
+ public:
+  ProxyServer(SocketFactory* int_factory, const SocketAddress& int_addr,
+              SocketFactory* ext_factory, const SocketAddress& ext_ip);
+  virtual ~ProxyServer();
+
+ protected:
+  void OnAcceptEvent(AsyncSocket* socket);
+  virtual AsyncProxyServerSocket* WrapSocket(AsyncSocket* socket) = 0;
+  void OnBindingDestroyed(ProxyBinding* binding);
+
+ private:
+  typedef std::list<ProxyBinding*> BindingList;
+  SocketFactory* ext_factory_;
+  SocketAddress ext_ip_;
+  scoped_ptr<AsyncSocket> server_socket_;
+  BindingList bindings_;
+  DISALLOW_EVIL_CONSTRUCTORS(ProxyServer);
+};
+
+// SocksProxyServer is a simple extension of ProxyServer to implement SOCKS.
+class SocksProxyServer : public ProxyServer {
+ public:
+  SocksProxyServer(SocketFactory* int_factory, const SocketAddress& int_addr,
+                   SocketFactory* ext_factory, const SocketAddress& ext_ip)
+      : ProxyServer(int_factory, int_addr, ext_factory, ext_ip) {
+  }
+ protected:
+  AsyncProxyServerSocket* WrapSocket(AsyncSocket* socket) {
+    return new AsyncSocksProxyServerSocket(socket);
+  }
+  DISALLOW_EVIL_CONSTRUCTORS(SocksProxyServer);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_PROXYSERVER_H_
diff --git a/talk/base/ratetracker.cc b/talk/base/ratetracker.cc
index d5207cd..383df93 100644
--- a/talk/base/ratetracker.cc
+++ b/talk/base/ratetracker.cc
@@ -26,7 +26,7 @@
  */
 
 #include "talk/base/ratetracker.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 
 namespace talk_base {
 
diff --git a/talk/base/ratetracker_unittest.cc b/talk/base/ratetracker_unittest.cc
new file mode 100644
index 0000000..979d907
--- /dev/null
+++ b/talk/base/ratetracker_unittest.cc
@@ -0,0 +1,91 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/ratetracker.h"
+
+namespace talk_base {
+
+class RateTrackerForTest : public RateTracker {
+ public:
+  RateTrackerForTest() : time_(0) {}
+  virtual uint32 Time() const { return time_; }
+  void AdvanceTime(uint32 delta) { time_ += delta; }
+
+ private:
+  uint32 time_;
+};
+
+TEST(RateTrackerTest, TestBasics) {
+  RateTrackerForTest tracker;
+  EXPECT_EQ(0U, tracker.total_units());
+  EXPECT_EQ(0U, tracker.units_second());
+
+  // Add a sample.
+  tracker.Update(1234);
+  // Advance the clock by 100 ms.
+  tracker.AdvanceTime(100);
+  // total_units should advance, but units_second should stay 0.
+  EXPECT_EQ(1234U, tracker.total_units());
+  EXPECT_EQ(0U, tracker.units_second());
+
+  // Repeat.
+  tracker.Update(1234);
+  tracker.AdvanceTime(100);
+  EXPECT_EQ(1234U * 2, tracker.total_units());
+  EXPECT_EQ(0U, tracker.units_second());
+
+  // Advance the clock by 800 ms, so we've elapsed a full second.
+  // units_second should now be filled in properly.
+  tracker.AdvanceTime(800);
+  EXPECT_EQ(1234U * 2, tracker.total_units());
+  EXPECT_EQ(1234U * 2, tracker.units_second());
+
+  // Poll the tracker again immediately. The reported rate should stay the same.
+  EXPECT_EQ(1234U * 2, tracker.total_units());
+  EXPECT_EQ(1234U * 2, tracker.units_second());
+
+  // Do nothing and advance by a second. We should drop down to zero.
+  tracker.AdvanceTime(1000);
+  EXPECT_EQ(1234U * 2, tracker.total_units());
+  EXPECT_EQ(0U, tracker.units_second());
+
+  // Send a bunch of data at a constant rate for 5.5 "seconds".
+  // We should report the rate properly.
+  for (int i = 0; i < 5500; i += 100) {
+    tracker.Update(9876U);
+    tracker.AdvanceTime(100);
+  }
+  EXPECT_EQ(9876U * 10, tracker.units_second());
+
+  // Advance the clock by 500 ms. Since we sent nothing over this half-second,
+  // the reported rate should be reduced by half.
+  tracker.AdvanceTime(500);
+  EXPECT_EQ(9876U * 5, tracker.units_second());
+}
+
+}  // namespace talk_base
diff --git a/talk/base/refcount.h b/talk/base/refcount.h
new file mode 100644
index 0000000..ce49d9e
--- /dev/null
+++ b/talk/base/refcount.h
@@ -0,0 +1,90 @@
+/*
+ * 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_APP_BASE_REFCOUNT_H_
+#define TALK_APP_BASE_REFCOUNT_H_
+
+#include <cstring>
+
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+// Reference count interface.
+class RefCountInterface {
+ public:
+  virtual int AddRef() = 0;
+  virtual int Release() = 0;
+};
+
+template <class T>
+class RefCountedObject : public T {
+ public:
+  RefCountedObject() : ref_count_(0) {
+  }
+
+  template<typename P>
+  explicit RefCountedObject(P p) : T(p), ref_count_(0) {
+  }
+
+  template<typename P1, typename P2>
+  RefCountedObject(P1 p1, P2 p2) : T(p1, p2), ref_count_(0) {
+  }
+
+  template<typename P1, typename P2, typename P3>
+  RefCountedObject(P1 p1, P2 p2, P3 p3) : T(p1, p2, p3), ref_count_(0) {
+  }
+
+  template<typename P1, typename P2, typename P3, typename P4>
+  RefCountedObject(P1 p1, P2 p2, P3 p3, P4 p4) 
+      : T(p1, p2, p3, p4), ref_count_(0) {
+  }
+
+  template<typename P1, typename P2, typename P3, typename P4, typename P5>
+  RefCountedObject(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)
+      : T(p1, p2, p3, p4, p5), ref_count_(0) {
+  }
+
+  virtual int AddRef() {
+    return talk_base::AtomicOps::Increment(&ref_count_);
+  }
+
+  virtual int Release() {
+    int count = talk_base::AtomicOps::Decrement(&ref_count_);
+    if (!count) {
+      delete this;
+    }
+    return count;
+  }
+
+ protected:
+  int ref_count_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_APP_BASE_REFCOUNT_H_
diff --git a/talk/base/rollingaccumulator.h b/talk/base/rollingaccumulator.h
new file mode 100644
index 0000000..3868031
--- /dev/null
+++ b/talk/base/rollingaccumulator.h
@@ -0,0 +1,116 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_ROLLINGACCUMULATOR_H_
+#define TALK_BASE_ROLLINGACCUMULATOR_H_
+
+#include <vector>
+
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+// RollingAccumulator stores and reports statistics
+// over N most recent samples.
+//
+// T is assumed to be an int, long, double or float.
+template<typename T>
+class RollingAccumulator {
+ public:
+  explicit RollingAccumulator(size_t max_count)
+    : count_(0),
+      next_index_(0),
+      sum_(0.0),
+      sum_2_(0.0),
+      samples_(max_count) {
+  }
+  ~RollingAccumulator() {
+  }
+
+  int max_count() const {
+    return samples_.size();
+  }
+
+  int count() const {
+    return count_;
+  }
+
+  void AddSample(T sample) {
+    if (count_ == max_count()) {
+      // Remove oldest sample.
+      T sample_to_remove = samples_[next_index_];
+      sum_ -= sample_to_remove;
+      sum_2_ -= sample_to_remove * sample_to_remove;
+    } else {
+      // Increase count of samples.
+      ++count_;
+    }
+    // Add new sample.
+    samples_[next_index_] = sample;
+    sum_ += sample;
+    sum_2_ += sample * sample;
+    // Update next_index_.
+    next_index_ = (next_index_ + 1) % max_count();
+  }
+
+  T ComputeSum() const {
+    return static_cast<T>(sum_);
+  }
+
+  T ComputeMean() const {
+    if (count_ == 0) {
+      return static_cast<T>(0);
+    }
+    return static_cast<T>(sum_ / count_);
+  }
+
+  // Compute estimated variance.  Estimation is more accurate
+  // as the number of samples grows.
+  T ComputeVariance() const {
+    if (count_ == 0) {
+      return static_cast<T>(0);
+    }
+    // Var = E[x^2] - (E[x])^2
+    double count_inv = 1.0 / count_;
+    double mean_2 = sum_2_ * count_inv;
+    double mean = sum_ * count_inv;
+    return static_cast<T>(mean_2 - (mean * mean));
+  }
+
+ private:
+  int count_;
+  int next_index_;
+  double sum_;    // Sum(x)
+  double sum_2_;  // Sum(x*x)
+  std::vector<T> samples_;
+
+  DISALLOW_COPY_AND_ASSIGN(RollingAccumulator);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ROLLINGACCUMULATOR_H_
diff --git a/talk/base/rollingaccumulator_unittest.cc b/talk/base/rollingaccumulator_unittest.cc
new file mode 100644
index 0000000..3630e5b
--- /dev/null
+++ b/talk/base/rollingaccumulator_unittest.cc
@@ -0,0 +1,81 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/rollingaccumulator.h"
+
+namespace talk_base {
+
+static bool IsApproximate(double expected, double value, double eps) {
+  return expected - eps <= value && value <= expected + eps;
+}
+
+TEST(RollingAccumulatorTest, TestZeroSamples) {
+  RollingAccumulator<int> accum(10);
+
+  EXPECT_EQ(0, accum.count());
+  EXPECT_EQ(0, accum.ComputeMean());
+  EXPECT_EQ(0, accum.ComputeVariance());
+}
+
+TEST(RollingAccumulatorTest, TestSomeSamples) {
+  RollingAccumulator<int> accum(10);
+  for (int i = 0; i < 4; ++i) {
+    accum.AddSample(i);
+  }
+
+  EXPECT_EQ(4, accum.count());
+  EXPECT_EQ(6, accum.ComputeSum());
+  EXPECT_EQ(1, accum.ComputeMean());
+  EXPECT_EQ(1, accum.ComputeVariance());
+}
+
+TEST(RollingAccumulatorTest, TestRollingSamples) {
+  RollingAccumulator<int> accum(10);
+  for (int i = 0; i < 12; ++i) {
+    accum.AddSample(i);
+  }
+
+  EXPECT_EQ(10, accum.count());
+  EXPECT_EQ(65, accum.ComputeSum());
+  EXPECT_EQ(6, accum.ComputeMean());
+  EXPECT_TRUE(IsApproximate(9, accum.ComputeVariance(), 1));
+}
+
+TEST(RollingAccumulatorTest, TestRollingSamplesDouble) {
+  RollingAccumulator<double> accum(10);
+  for (int i = 0; i < 23; ++i) {
+    accum.AddSample(5 * i);
+  }
+
+  EXPECT_EQ(10, accum.count());
+  EXPECT_TRUE(IsApproximate(875.0, accum.ComputeSum(), 1E-6));
+  EXPECT_TRUE(IsApproximate(87.5, accum.ComputeMean(), 1E-6));
+  EXPECT_TRUE(IsApproximate(229.166667, accum.ComputeVariance(), 25));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/scoped_autorelease_pool.h b/talk/base/scoped_autorelease_pool.h
new file mode 100644
index 0000000..611f811
--- /dev/null
+++ b/talk/base/scoped_autorelease_pool.h
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Automatically initialize and and free an autoreleasepool. Never allocate
+// an instance of this class using "new" - that will result in a compile-time
+// error. Only use it as a stack object.
+//
+// Note: NSAutoreleasePool docs say that you should not normally need to
+// declare an NSAutoreleasePool as a member of an object - but there's nothing
+// that indicates it will be a problem, as long as the stack lifetime of the
+// pool exactly matches the stack lifetime of the object.
+
+#ifndef TALK_BASE_SCOPED_AUTORELEASE_POOL_H__
+#define TALK_BASE_SCOPED_AUTORELEASE_POOL_H__
+
+#if defined(IOS) || defined(OSX)
+
+#include "talk/base/common.h"
+
+// This header may be included from Obj-C files or C++ files.
+#ifdef __OBJC__
+@class NSAutoreleasePool;
+#else
+class NSAutoreleasePool;
+#endif
+
+namespace talk_base {
+
+class ScopedAutoreleasePool {
+ public:
+  ScopedAutoreleasePool();
+  ~ScopedAutoreleasePool();
+
+ private:
+  // Declaring private overrides of new and delete here enforces the "only use
+  // as a stack object" discipline.
+  //
+  // Note: new is declared as "throw()" to get around a gcc warning about new
+  // returning NULL, but this method will never get called and therefore will
+  // never actually throw any exception.
+  void* operator new(size_t size) throw() { return NULL; }
+  void operator delete (void* ptr) {}
+
+  NSAutoreleasePool* pool_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(ScopedAutoreleasePool);
+};
+
+}  // namespace talk_base
+
+#endif  // IOS || OSX
+#endif  // TALK_BASE_SCOPED_AUTORELEASE_POOL_H__
diff --git a/talk/base/scoped_autorelease_pool.mm b/talk/base/scoped_autorelease_pool.mm
new file mode 100644
index 0000000..4009c7b
--- /dev/null
+++ b/talk/base/scoped_autorelease_pool.mm
@@ -0,0 +1,42 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "talk/base/scoped_autorelease_pool.h"
+
+namespace talk_base {
+
+ScopedAutoreleasePool::ScopedAutoreleasePool() {
+  pool_ = [[NSAutoreleasePool alloc] init];
+}
+
+ScopedAutoreleasePool::~ScopedAutoreleasePool() {
+  [pool_ drain];
+}
+
+}  // namespace talk_base
diff --git a/talk/base/scoped_ref_ptr.h b/talk/base/scoped_ref_ptr.h
new file mode 100644
index 0000000..3ce72cb
--- /dev/null
+++ b/talk/base/scoped_ref_ptr.h
@@ -0,0 +1,162 @@
+/*
+ * 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.
+ */
+
+// Originally these classes are from Chromium.
+// http://src.chromium.org/viewvc/chrome/trunk/src/base/memory/ref_counted.h?view=markup
+
+//
+// A smart pointer class for reference counted objects.  Use this class instead
+// of calling AddRef and Release manually on a reference counted object to
+// avoid common memory leaks caused by forgetting to Release an object
+// reference.  Sample usage:
+//
+//   class MyFoo : public RefCounted<MyFoo> {
+//    ...
+//   };
+//
+//   void some_function() {
+//     scoped_refptr<MyFoo> foo = new MyFoo();
+//     foo->Method(param);
+//     // |foo| is released when this function returns
+//   }
+//
+//   void some_other_function() {
+//     scoped_refptr<MyFoo> foo = new MyFoo();
+//     ...
+//     foo = NULL;  // explicitly releases |foo|
+//     ...
+//     if (foo)
+//       foo->Method(param);
+//   }
+//
+// The above examples show how scoped_refptr<T> acts like a pointer to T.
+// Given two scoped_refptr<T> classes, it is also possible to exchange
+// references between the two objects, like so:
+//
+//   {
+//     scoped_refptr<MyFoo> a = new MyFoo();
+//     scoped_refptr<MyFoo> b;
+//
+//     b.swap(a);
+//     // now, |b| references the MyFoo object, and |a| references NULL.
+//   }
+//
+// To make both |a| and |b| in the above example reference the same MyFoo
+// object, simply use the assignment operator:
+//
+//   {
+//     scoped_refptr<MyFoo> a = new MyFoo();
+//     scoped_refptr<MyFoo> b;
+//
+//     b = a;
+//     // now, |a| and |b| each own a reference to the same MyFoo object.
+//   }
+//
+
+#ifndef TALK_BASE_SCOPED_REF_PTR_H_
+#define TALK_BASE_SCOPED_REF_PTR_H_
+
+namespace talk_base {
+
+template <class T>
+class scoped_refptr {
+ public:
+  scoped_refptr() : ptr_(NULL) {
+  }
+
+  scoped_refptr(T* p) : ptr_(p) {
+    if (ptr_)
+      ptr_->AddRef();
+  }
+
+  scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) {
+    if (ptr_)
+      ptr_->AddRef();
+  }
+
+  template <typename U>
+  scoped_refptr(const scoped_refptr<U>& r) : ptr_(r.get()) {
+    if (ptr_)
+      ptr_->AddRef();
+  }
+
+  ~scoped_refptr() {
+    if (ptr_)
+      ptr_->Release();
+  }
+
+  T* get() const { return ptr_; }
+  operator T*() const { return ptr_; }
+  T* operator->() const { return ptr_; }
+
+  // Release a pointer.
+  // The return value is the current pointer held by this object.
+  // If this object holds a NULL pointer, the return value is NULL.
+  // After this operation, this object will hold a NULL pointer,
+  // and will not own the object any more.
+  T* release() {
+    T* retVal = ptr_;
+    ptr_ = NULL;
+    return retVal;
+  }
+
+  scoped_refptr<T>& operator=(T* p) {
+    // AddRef first so that self assignment should work
+    if (p)
+      p->AddRef();
+    if (ptr_ )
+      ptr_ ->Release();
+    ptr_ = p;
+    return *this;
+  }
+
+  scoped_refptr<T>& operator=(const scoped_refptr<T>& r) {
+    return *this = r.ptr_;
+  }
+
+  template <typename U>
+  scoped_refptr<T>& operator=(const scoped_refptr<U>& r) {
+    return *this = r.get();
+  }
+
+  void swap(T** pp) {
+    T* p = ptr_;
+    ptr_ = *pp;
+    *pp = p;
+  }
+
+  void swap(scoped_refptr<T>& r) {
+    swap(&r.ptr_);
+  }
+
+ protected:
+  T* ptr_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SCOPED_REF_PTR_H_
diff --git a/talk/base/sharedexclusivelock.cc b/talk/base/sharedexclusivelock.cc
new file mode 100644
index 0000000..0b0439a
--- /dev/null
+++ b/talk/base/sharedexclusivelock.cc
@@ -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.
+ */
+
+#include "talk/base/sharedexclusivelock.h"
+
+namespace talk_base {
+
+SharedExclusiveLock::SharedExclusiveLock()
+    : shared_count_is_zero_(true, true),
+      shared_count_(0) {
+}
+
+void SharedExclusiveLock::LockExclusive() {
+  cs_exclusive_.Enter();
+  shared_count_is_zero_.Wait(talk_base::kForever);
+}
+
+void SharedExclusiveLock::UnlockExclusive() {
+  cs_exclusive_.Leave();
+}
+
+void SharedExclusiveLock::LockShared() {
+  CritScope exclusive_scope(&cs_exclusive_);
+  CritScope shared_scope(&cs_shared_);
+  if (++shared_count_ == 1) {
+    shared_count_is_zero_.Reset();
+  }
+}
+
+void SharedExclusiveLock::UnlockShared() {
+  CritScope shared_scope(&cs_shared_);
+  if (--shared_count_ == 0) {
+    shared_count_is_zero_.Set();
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/sharedexclusivelock.h b/talk/base/sharedexclusivelock.h
new file mode 100644
index 0000000..2bdd854
--- /dev/null
+++ b/talk/base/sharedexclusivelock.h
@@ -0,0 +1,93 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SHAREDEXCLUSIVELOCK_H_
+#define TALK_BASE_SHAREDEXCLUSIVELOCK_H_
+
+#include "talk/base/constructormagic.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/event.h"
+
+namespace talk_base {
+
+// This class provides shared-exclusive lock. It can be used in cases like
+// multiple-readers/single-writer model.
+class SharedExclusiveLock {
+ public:
+  SharedExclusiveLock();
+
+  // Locking/unlocking methods. It is encouraged to use SharedScope or
+  // ExclusiveScope for protection.
+  void LockExclusive();
+  void UnlockExclusive();
+  void LockShared();
+  void UnlockShared();
+
+ private:
+  talk_base::CriticalSection cs_exclusive_;
+  talk_base::CriticalSection cs_shared_;
+  talk_base::Event shared_count_is_zero_;
+  int shared_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(SharedExclusiveLock);
+};
+
+class SharedScope {
+ public:
+  explicit SharedScope(SharedExclusiveLock* lock) : lock_(lock) {
+    lock_->LockShared();
+  }
+
+  ~SharedScope() {
+    lock_->UnlockShared();
+  }
+
+ private:
+  SharedExclusiveLock* lock_;
+
+  DISALLOW_COPY_AND_ASSIGN(SharedScope);
+};
+
+class ExclusiveScope {
+ public:
+  explicit ExclusiveScope(SharedExclusiveLock* lock) : lock_(lock) {
+    lock_->LockExclusive();
+  }
+
+  ~ExclusiveScope() {
+    lock_->UnlockExclusive();
+  }
+
+ private:
+  SharedExclusiveLock* lock_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExclusiveScope);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SHAREDEXCLUSIVELOCK_H_
diff --git a/talk/base/sharedexclusivelock_unittest.cc b/talk/base/sharedexclusivelock_unittest.cc
new file mode 100644
index 0000000..46b7fdf
--- /dev/null
+++ b/talk/base/sharedexclusivelock_unittest.cc
@@ -0,0 +1,234 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sharedexclusivelock.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+
+static const uint32 kMsgRead = 0;
+static const uint32 kMsgWrite = 0;
+static const int kNoWaitThresholdInMs = 10;
+static const int kWaitThresholdInMs = 80;
+static const int kProcessTimeInMs = 100;
+static const int kProcessTimeoutInMs = 5000;
+
+class SharedExclusiveTask : public MessageHandler {
+ public:
+  SharedExclusiveTask(SharedExclusiveLock* shared_exclusive_lock,
+                      int* value,
+                      bool* done)
+      : shared_exclusive_lock_(shared_exclusive_lock),
+        waiting_time_in_ms_(0),
+        value_(value),
+        done_(done) {
+    worker_thread_.reset(new Thread());
+    worker_thread_->Start();
+  }
+
+  int waiting_time_in_ms() const { return waiting_time_in_ms_; }
+
+ protected:
+  scoped_ptr<Thread> worker_thread_;
+  SharedExclusiveLock* shared_exclusive_lock_;
+  int waiting_time_in_ms_;
+  int* value_;
+  bool* done_;
+};
+
+class ReadTask : public SharedExclusiveTask {
+ public:
+  ReadTask(SharedExclusiveLock* shared_exclusive_lock, int* value, bool* done)
+      : SharedExclusiveTask(shared_exclusive_lock, value, done) {
+  }
+
+  void PostRead(int* value) {
+    worker_thread_->Post(this, kMsgRead, new TypedMessageData<int*>(value));
+  }
+
+ private:
+  virtual void OnMessage(Message* message) {
+    ASSERT(talk_base::Thread::Current() == worker_thread_.get());
+    ASSERT(message != NULL);
+    ASSERT(message->message_id == kMsgRead);
+
+    TypedMessageData<int*>* message_data =
+        static_cast<TypedMessageData<int*>*>(message->pdata);
+
+    uint32 start_time = Time();
+    {
+      SharedScope ss(shared_exclusive_lock_);
+      waiting_time_in_ms_ = TimeDiff(Time(), start_time);
+
+      Thread::SleepMs(kProcessTimeInMs);
+      *message_data->data() = *value_;
+      *done_ = true;
+    }
+    delete message->pdata;
+    message->pdata = NULL;
+  }
+};
+
+class WriteTask : public SharedExclusiveTask {
+ public:
+  WriteTask(SharedExclusiveLock* shared_exclusive_lock, int* value, bool* done)
+      : SharedExclusiveTask(shared_exclusive_lock, value, done) {
+  }
+
+  void PostWrite(int value) {
+    worker_thread_->Post(this, kMsgWrite, new TypedMessageData<int>(value));
+  }
+
+ private:
+  virtual void OnMessage(Message* message) {
+    ASSERT(talk_base::Thread::Current() == worker_thread_.get());
+    ASSERT(message != NULL);
+    ASSERT(message->message_id == kMsgWrite);
+
+    TypedMessageData<int>* message_data =
+        static_cast<TypedMessageData<int>*>(message->pdata);
+
+    uint32 start_time = Time();
+    {
+      ExclusiveScope es(shared_exclusive_lock_);
+      waiting_time_in_ms_ = TimeDiff(Time(), start_time);
+
+      Thread::SleepMs(kProcessTimeInMs);
+      *value_ = message_data->data();
+      *done_ = true;
+    }
+    delete message->pdata;
+    message->pdata = NULL;
+  }
+};
+
+// Unit test for SharedExclusiveLock.
+class SharedExclusiveLockTest
+    : public testing::Test {
+ public:
+  SharedExclusiveLockTest() : value_(0) {
+  }
+
+  virtual void SetUp() {
+    shared_exclusive_lock_.reset(new SharedExclusiveLock());
+  }
+
+ protected:
+  scoped_ptr<SharedExclusiveLock> shared_exclusive_lock_;
+  int value_;
+};
+
+TEST_F(SharedExclusiveLockTest, TestSharedShared) {
+  int value0, value1;
+  bool done0, done1;
+  ReadTask reader0(shared_exclusive_lock_.get(), &value_, &done0);
+  ReadTask reader1(shared_exclusive_lock_.get(), &value_, &done1);
+
+  // Test shared locks can be shared without waiting.
+  {
+    SharedScope ss(shared_exclusive_lock_.get());
+    value_ = 1;
+    done0 = false;
+    done1 = false;
+    reader0.PostRead(&value0);
+    reader1.PostRead(&value1);
+    Thread::SleepMs(kProcessTimeInMs);
+  }
+
+  EXPECT_TRUE_WAIT(done0, kProcessTimeoutInMs);
+  EXPECT_EQ(1, value0);
+  EXPECT_LE(reader0.waiting_time_in_ms(), kNoWaitThresholdInMs);
+  EXPECT_TRUE_WAIT(done1, kProcessTimeoutInMs);
+  EXPECT_EQ(1, value1);
+  EXPECT_LE(reader1.waiting_time_in_ms(), kNoWaitThresholdInMs);
+}
+
+TEST_F(SharedExclusiveLockTest, TestSharedExclusive) {
+  bool done;
+  WriteTask writer(shared_exclusive_lock_.get(), &value_, &done);
+
+  // Test exclusive lock needs to wait for shared lock.
+  {
+    SharedScope ss(shared_exclusive_lock_.get());
+    value_ = 1;
+    done = false;
+    writer.PostWrite(2);
+    Thread::SleepMs(kProcessTimeInMs);
+    EXPECT_EQ(1, value_);
+  }
+
+  EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs);
+  EXPECT_EQ(2, value_);
+  EXPECT_GE(writer.waiting_time_in_ms(), kWaitThresholdInMs);
+}
+
+TEST_F(SharedExclusiveLockTest, TestExclusiveShared) {
+  int value;
+  bool done;
+  ReadTask reader(shared_exclusive_lock_.get(), &value_, &done);
+
+  // Test shared lock needs to wait for exclusive lock.
+  {
+    ExclusiveScope es(shared_exclusive_lock_.get());
+    value_ = 1;
+    done = false;
+    reader.PostRead(&value);
+    Thread::SleepMs(kProcessTimeInMs);
+    value_ = 2;
+  }
+
+  EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs);
+  EXPECT_EQ(2, value);
+  EXPECT_GE(reader.waiting_time_in_ms(), kWaitThresholdInMs);
+}
+
+TEST_F(SharedExclusiveLockTest, TestExclusiveExclusive) {
+  bool done;
+  WriteTask writer(shared_exclusive_lock_.get(), &value_, &done);
+
+  // Test exclusive lock needs to wait for exclusive lock.
+  {
+    ExclusiveScope es(shared_exclusive_lock_.get());
+    value_ = 1;
+    done = false;
+    writer.PostWrite(2);
+    Thread::SleepMs(kProcessTimeInMs);
+    EXPECT_EQ(1, value_);
+  }
+
+  EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs);
+  EXPECT_EQ(2, value_);
+  EXPECT_GE(writer.waiting_time_in_ms(), kWaitThresholdInMs);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/socket.h b/talk/base/socket.h
index a55b3dc..ad8eee6 100644
--- a/talk/base/socket.h
+++ b/talk/base/socket.h
@@ -180,9 +180,10 @@
 
   enum Option {
     OPT_DONTFRAGMENT,
-    OPT_RCVBUF,  // receive buffer size
-    OPT_SNDBUF,  // send buffer size
-    OPT_NODELAY  // whether Nagle algorithm is enabled
+    OPT_RCVBUF,      // receive buffer size
+    OPT_SNDBUF,      // send buffer size
+    OPT_NODELAY,     // whether Nagle algorithm is enabled
+    OPT_IPV6_V6ONLY  // Whether the socket is IPv6 only.
   };
   virtual int GetOption(Option opt, int* value) = 0;
   virtual int SetOption(Option opt, int value) = 0;
diff --git a/talk/base/socket_unittest.cc b/talk/base/socket_unittest.cc
new file mode 100644
index 0000000..8d5b3c5
--- /dev/null
+++ b/talk/base/socket_unittest.cc
@@ -0,0 +1,731 @@
+/*
+ * libjingle
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/socket_unittest.h"
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/gunit.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/testclient.h"
+#include "talk/base/testutils.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+static const SocketAddress kEmptyAddr;
+static const SocketAddress kLoopbackAddr(IPAddress(INADDR_LOOPBACK), 0);
+
+void SocketTest::TestConnect() {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+  EXPECT_EQ(AsyncSocket::CS_CLOSED, client->GetState());
+  EXPECT_EQ(kEmptyAddr, client->GetLocalAddress());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+  EXPECT_EQ(0, server->Listen(5));
+  EXPECT_EQ(AsyncSocket::CS_CONNECTING, server->GetState());
+
+  // Ensure no pending server connections, since we haven't done anything yet.
+  EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ));
+  EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+  EXPECT_EQ(kEmptyAddr, accept_addr);
+
+  // Attempt connect to listening socket.
+  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+  EXPECT_NE(kEmptyAddr, client->GetLocalAddress());  // Implicit Bind
+  EXPECT_NE(server->GetLocalAddress(), client->GetLocalAddress());
+
+  // Client is connecting, outcome not yet determined.
+  EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState());
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+
+  // Server has pending connection, accept it.
+  EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+  scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+  ASSERT_TRUE(NULL != accepted.get());
+  EXPECT_NE(kEmptyAddr, accept_addr);
+  EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr);
+
+  // Connected from server perspective, check the addresses are correct.
+  EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState());
+  EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress());
+  EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress());
+
+  // Connected from client perspective, check the addresses are correct.
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+  EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+  EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+}
+
+void SocketTest::TestConnectWithDnsLookup() {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+  EXPECT_EQ(0, server->Listen(5));
+
+  // Attempt connect to listening socket.
+  SocketAddress dns_addr(server->GetLocalAddress());
+  dns_addr.SetIP("localhost");
+  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+  // TODO: Bind when doing DNS lookup.
+  //EXPECT_NE(kEmptyAddr, client->GetLocalAddress());  // Implicit Bind
+
+  // Client is connecting, outcome not yet determined.
+  EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState());
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+
+  // Server has pending connection, accept it.
+  EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+  scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+  ASSERT_TRUE(NULL != accepted.get());
+  EXPECT_NE(kEmptyAddr, accept_addr);
+  EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr);
+
+  // Connected from server perspective, check the addresses are correct.
+  EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState());
+  EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress());
+  EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress());
+
+  // Connected from client perspective, check the addresses are correct.
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+  EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+  EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+}
+
+void SocketTest::TestConnectFail() {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server, but don't listen yet.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+
+  // Attempt connect to a non-existent socket.
+  // We don't connect to the server socket created above, since on
+  // MacOS it takes about 75 seconds to get back an error!
+  SocketAddress bogus_addr(IPAddress(INADDR_LOOPBACK), 65535);
+  EXPECT_EQ(0, client->Connect(bogus_addr));
+
+  // Wait for connection to fail (ECONNREFUSED).
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR));
+  EXPECT_EQ(kEmptyAddr, client->GetRemoteAddress());
+
+  // Should be no pending server connections.
+  EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ));
+  EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+  EXPECT_EQ(kEmptyAddr, accept_addr);
+}
+
+void SocketTest::TestConnectWithDnsLookupFail() {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server, but don't listen yet.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+
+  // Attempt connect to a non-existent host.
+  // We don't connect to the server socket created above, since on
+  // MacOS it takes about 75 seconds to get back an error!
+  SocketAddress bogus_dns_addr("not-a-real-hostname", 65535);
+  EXPECT_EQ(0, client->Connect(bogus_dns_addr));
+
+  // Wait for connection to fail (EHOSTNOTFOUND).
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR));
+  EXPECT_EQ(kEmptyAddr, client->GetRemoteAddress());
+
+  // Should be no pending server connections.
+  EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ));
+  EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+  EXPECT_EQ(kEmptyAddr, accept_addr);
+}
+
+void SocketTest::TestConnectWithClosedSocket() {
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+  EXPECT_EQ(0, server->Listen(5));
+
+  // Create a client and put in to CS_CLOSED state.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  EXPECT_EQ(0, client->Close());
+  EXPECT_EQ(AsyncSocket::CS_CLOSED, client->GetState());
+
+  // Connect() should reinitialize the socket, and put it in to CS_CONNECTING.
+  EXPECT_EQ(0, client->Connect(SocketAddress(server->GetLocalAddress())));
+  EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState());
+}
+
+void SocketTest::TestServerCloseDuringConnect() {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+  EXPECT_EQ(0, server->Listen(5));
+
+  // Attempt connect to listening socket.
+  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+  // Close down the server while the socket is in the accept queue.
+  EXPECT_TRUE_WAIT(sink.Check(server.get(), testing::SSE_READ), kTimeout);
+  server->Close();
+
+  // This should fail the connection for the client. Clean up.
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR));
+  client->Close();
+}
+
+void SocketTest::TestClientCloseDuringConnect() {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+  EXPECT_EQ(0, server->Listen(5));
+
+  // Attempt connect to listening socket.
+  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+  // Close down the client while the socket is in the accept queue.
+  EXPECT_TRUE_WAIT(sink.Check(server.get(), testing::SSE_READ), kTimeout);
+  client->Close();
+
+  // The connection should still be able to be accepted.
+  scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+  ASSERT_TRUE(NULL != accepted.get());
+  sink.Monitor(accepted.get());
+  EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState());
+
+  // The accepted socket should then close (possibly with err, timing-related)
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, accepted->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(accepted.get(), testing::SSE_CLOSE) ||
+              sink.Check(accepted.get(), testing::SSE_ERROR));
+
+  // The client should not get a close event.
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+}
+
+void SocketTest::TestServerClose() {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+  EXPECT_EQ(0, server->Listen(5));
+
+  // Attempt connection.
+  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+  // Accept connection.
+  EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+  scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+  ASSERT_TRUE(NULL != accepted.get());
+  sink.Monitor(accepted.get());
+
+  // Both sides are now connected.
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+  EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+  // Send data to the client, and then close the connection.
+  EXPECT_EQ(1, accepted->Send("a", 1));
+  accepted->Close();
+  EXPECT_EQ(AsyncSocket::CS_CLOSED, accepted->GetState());
+
+  // Expect that the client is notified, and has not yet closed.
+  EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout);
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+  EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState());
+
+  // Ensure the data can be read.
+  char buffer[10];
+  EXPECT_EQ(1, client->Recv(buffer, sizeof(buffer)));
+  EXPECT_EQ('a', buffer[0]);
+
+  // Now we should close, but the remote address will remain.
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE));
+  EXPECT_NE(kEmptyAddr, client->GetRemoteAddress());
+
+  // The closer should not get a close signal.
+  EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_CLOSE));
+  EXPECT_EQ(kEmptyAddr, accepted->GetRemoteAddress());
+
+  // And the closee should only get a single signal.
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+
+  // Close down the client and ensure all is good.
+  client->Close();
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+  EXPECT_EQ(kEmptyAddr, client->GetRemoteAddress());
+}
+
+class SocketCloser : public sigslot::has_slots<> {
+ public:
+  void OnClose(AsyncSocket* socket, int error) {
+    socket->Close();  // Deleting here would blow up the vector of handlers
+                      // for the socket's signal.
+  }
+};
+
+void SocketTest::TestCloseInClosedCallback() {
+  testing::StreamSink sink;
+  SocketCloser closer;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+  client->SignalCloseEvent.connect(&closer, &SocketCloser::OnClose);
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+  EXPECT_EQ(0, server->Listen(5));
+
+  // Attempt connection.
+  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+  // Accept connection.
+  EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+  scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+  ASSERT_TRUE(NULL != accepted.get());
+  sink.Monitor(accepted.get());
+
+  // Both sides are now connected.
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+  EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+  // Send data to the client, and then close the connection.
+  accepted->Close();
+  EXPECT_EQ(AsyncSocket::CS_CLOSED, accepted->GetState());
+
+  // Expect that the client is notified, and has not yet closed.
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+  EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState());
+
+  // Now we should be closed and invalidated
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE));
+  EXPECT_TRUE(Socket::CS_CLOSED == client->GetState());
+}
+
+class Sleeper : public MessageHandler {
+ public:
+  Sleeper() {}
+  void OnMessage(Message* msg) {
+    Thread::Current()->SleepMs(500);
+  }
+};
+
+void SocketTest::TestSocketServerWait() {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create & connect server and client sockets.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+  EXPECT_EQ(0, server->Listen(5));
+
+  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+  EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+
+  scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+  ASSERT_TRUE(NULL != accepted.get());
+  sink.Monitor(accepted.get());
+  EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState());
+  EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress());
+  EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress());
+
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+  EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+  EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+
+  // Do an i/o operation, triggering an eventual callback.
+  EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ));
+  char buf[1024] = {0};
+  
+  EXPECT_EQ(1024, client->Send(buf, 1024));
+  EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ));
+
+  // Shouldn't signal when blocked in a thread Send, where process_io is false.
+  scoped_ptr<Thread> thread(new Thread());
+  thread->Start();
+  Sleeper sleeper;
+  TypedMessageData<AsyncSocket*> data(client.get());
+  thread->Send(&sleeper, 0, &data);
+  EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ));
+
+  // But should signal when process_io is true.
+  EXPECT_TRUE_WAIT((sink.Check(accepted.get(), testing::SSE_READ)), kTimeout);
+  EXPECT_LT(0, accepted->Recv(buf, 1024));
+}
+
+void SocketTest::TestTcp() {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create test data.
+  const size_t kDataSize = 1024 * 1024;
+  scoped_array<char> send_buffer(new char[kDataSize]);
+  scoped_array<char> recv_buffer(new char[kDataSize]);
+  size_t send_pos = 0, recv_pos = 0;
+  for (size_t i = 0; i < kDataSize; ++i) {
+    send_buffer[i] = i;
+    recv_buffer[i] = 0;
+  }
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+  EXPECT_EQ(0, server->Listen(5));
+
+  // Attempt connection.
+  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+  // Accept connection.
+  EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+  scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+  ASSERT_TRUE(NULL != accepted.get());
+  sink.Monitor(accepted.get());
+
+  // Both sides are now connected.
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+  EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+  // Send and receive a bunch of data.
+  bool send_waiting_for_writability = false;
+  bool send_expect_success = true;
+  bool recv_waiting_for_readability = true;
+  bool recv_expect_success = false;
+  int data_in_flight = 0;
+  while (recv_pos < kDataSize) {
+    // Send as much as we can if we've been cleared to send.
+    while (!send_waiting_for_writability && send_pos < kDataSize) {
+      int tosend = kDataSize - send_pos;
+      int sent = accepted->Send(send_buffer.get() + send_pos, tosend);
+      if (send_expect_success) {
+        // The first Send() after connecting or getting writability should
+        // succeed and send some data.
+        EXPECT_GT(sent, 0);
+        send_expect_success = false;
+      }
+      if (sent >= 0) {
+        EXPECT_LE(sent, tosend);
+        send_pos += sent;
+        data_in_flight += sent;
+      } else {
+        ASSERT_TRUE(accepted->IsBlocking());
+        send_waiting_for_writability = true;
+      }
+    }
+
+    // Read all the sent data.
+    while (data_in_flight > 0) {
+      if (recv_waiting_for_readability) {
+        // Wait until data is available.
+        EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout);
+        recv_waiting_for_readability = false;
+        recv_expect_success = true;
+      }
+
+      // Receive as much as we can get in a single recv call.
+      int rcvd = client->Recv(recv_buffer.get() + recv_pos,
+                              kDataSize - recv_pos);
+
+      if (recv_expect_success) {
+        // The first Recv() after getting readability should succeed and receive
+        // some data.
+        EXPECT_GT(rcvd, 0);
+        recv_expect_success = false;
+      }
+      if (rcvd >= 0) {
+        EXPECT_LE(rcvd, data_in_flight);
+        recv_pos += rcvd;
+        data_in_flight -= rcvd;
+      } else {
+        ASSERT_TRUE(client->IsBlocking());
+        recv_waiting_for_readability = true;
+      }
+    }
+
+    // Once all that we've sent has been rcvd, expect to be able to send again.
+    if (send_waiting_for_writability) {
+      EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE),
+                       kTimeout);
+      send_waiting_for_writability = false;
+      send_expect_success = true;
+    }
+  }
+
+  // The received data matches the sent data.
+  EXPECT_EQ(kDataSize, send_pos);
+  EXPECT_EQ(kDataSize, recv_pos);
+  EXPECT_EQ(0, memcmp(recv_buffer.get(), send_buffer.get(), kDataSize));
+
+  // Close down.
+  accepted->Close();
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE));
+  client->Close();
+}
+
+void SocketTest::TestSingleFlowControlCallback() {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+  EXPECT_EQ(0, server->Listen(5));
+
+  // Attempt connection.
+  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+  // Accept connection.
+  EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+  scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+  ASSERT_TRUE(NULL != accepted.get());
+  sink.Monitor(accepted.get());
+
+  // Both sides are now connected.
+  EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+  EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+  EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+  EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+  // Fill the socket buffer.
+  char buf[1024 * 16] = {0};
+  while (accepted->Send(&buf, ARRAY_SIZE(buf)) != -1) {}
+  EXPECT_TRUE(accepted->IsBlocking());
+
+  // Expect no writable callbacks
+  EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_WRITE));
+
+  // Wait until data is available.
+  EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout);
+
+  // Pull some data.
+  client->Recv(buf, ARRAY_SIZE(buf));
+
+  // Expect at least one additional writable callback.
+  EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE), kTimeout);
+
+  // Adding data in response to the writeable callback shouldn't cause infinite
+  // callbacks.
+  int extras = 0;
+  for (int i = 0; i < 100; ++i) {
+    accepted->Send(&buf, ARRAY_SIZE(buf));
+    talk_base::Thread::Current()->ProcessMessages(1);
+    if (sink.Check(accepted.get(), testing::SSE_WRITE)) {
+      extras++;
+    }
+  }
+  EXPECT_LT(extras, 2);
+
+  // Close down.
+  accepted->Close();
+  client->Close();
+}
+
+void SocketTest::TestUdp() {
+  // Test basic bind and connect behavior.
+  SocketAddress addr1(kLoopbackAddr);
+  AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+  EXPECT_EQ(AsyncSocket::CS_CLOSED, socket->GetState());
+  EXPECT_EQ(0, socket->Bind(addr1));
+  addr1 = socket->GetLocalAddress();
+  EXPECT_EQ(0, socket->Connect(addr1));
+  EXPECT_EQ(AsyncSocket::CS_CONNECTED, socket->GetState());
+  socket->Close();
+  EXPECT_EQ(AsyncSocket::CS_CLOSED, socket->GetState());
+  delete socket;
+
+  // Test send/receive behavior.
+  scoped_ptr<TestClient> client1(new TestClient(
+      AsyncUDPSocket::Create(ss_, addr1)));
+  scoped_ptr<TestClient> client2(new TestClient(
+      AsyncUDPSocket::Create(ss_, SocketAddress())));
+
+  SocketAddress addr2;
+  EXPECT_EQ(3, client2->SendTo("foo", 3, addr1));
+  EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &addr2));
+
+  SocketAddress addr3;
+  EXPECT_EQ(6, client1->SendTo("bizbaz", 6, addr2));
+  EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &addr3));
+  EXPECT_EQ(addr3, addr1);
+
+  // TODO: figure out what the intent is here
+  for (int i = 0; i < 10; ++i) {
+    client2.reset(new TestClient(AsyncUDPSocket::Create(ss_, SocketAddress())));
+
+    SocketAddress addr4;
+    EXPECT_EQ(3, client2->SendTo("foo", 3, addr1));
+    EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &addr4));
+    EXPECT_EQ(addr4.ipaddr(), addr2.ipaddr());
+
+    SocketAddress addr5;
+    EXPECT_EQ(6, client1->SendTo("bizbaz", 6, addr4));
+    EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &addr5));
+    EXPECT_EQ(addr5, addr1);
+
+    addr2 = addr4;
+  }
+}
+
+void SocketTest::TestGetSetOptions() {
+  talk_base::scoped_ptr<AsyncSocket> socket(ss_->CreateAsyncSocket(SOCK_DGRAM));
+  socket->Bind(kLoopbackAddr);
+
+  // Check SNDBUF/RCVBUF.
+  const int desired_size = 12345;
+#if defined(LINUX) || defined(ANDROID)
+  // Yes, really.  It's in the kernel source.
+  const int expected_size = desired_size * 2;
+#else   // !LINUX && !ANDROID
+  const int expected_size = desired_size;
+#endif  // !LINUX && !ANDROID
+  int recv_size = 0;
+  int send_size = 0;
+  // get the initial sizes
+  ASSERT_NE(-1, socket->GetOption(Socket::OPT_RCVBUF, &recv_size));
+  ASSERT_NE(-1, socket->GetOption(Socket::OPT_SNDBUF, &send_size));
+  // set our desired sizes
+  ASSERT_NE(-1, socket->SetOption(Socket::OPT_RCVBUF, desired_size));
+  ASSERT_NE(-1, socket->SetOption(Socket::OPT_SNDBUF, desired_size));
+  // get the sizes again
+  ASSERT_NE(-1, socket->GetOption(Socket::OPT_RCVBUF, &recv_size));
+  ASSERT_NE(-1, socket->GetOption(Socket::OPT_SNDBUF, &send_size));
+  // make sure they are right
+  ASSERT_EQ(expected_size, recv_size);
+  ASSERT_EQ(expected_size, send_size);
+
+  // Check that we can't set NODELAY on a UDP socket.
+  int current_nd, desired_nd = 1;
+  ASSERT_EQ(-1, socket->GetOption(Socket::OPT_NODELAY, &current_nd));
+  ASSERT_EQ(-1, socket->SetOption(Socket::OPT_NODELAY, desired_nd));
+
+  // Try estimating MTU.
+  talk_base::scoped_ptr<AsyncSocket>
+      mtu_socket(ss_->CreateAsyncSocket(SOCK_DGRAM));
+  mtu_socket->Bind(kLoopbackAddr);
+  uint16 mtu;
+  // should fail until we connect
+  ASSERT_EQ(-1, mtu_socket->EstimateMTU(&mtu));
+  mtu_socket->Connect(kLoopbackAddr);
+#if defined(WIN32)
+  // now it should succeed
+  ASSERT_NE(-1, mtu_socket->EstimateMTU(&mtu));
+  ASSERT_GE(mtu, 1492);  // should be at least the 1492 "plateau" on localhost
+#elif defined(OSX)
+  // except on OSX, where it's not yet implemented
+  ASSERT_EQ(-1, mtu_socket->EstimateMTU(&mtu));
+#else
+  // and the behavior seems unpredictable on Linux, failing on the build machine
+  // but succeeding on my Ubiquity instance.
+#endif
+}
+
+}  // namespace talk_base
diff --git a/talk/base/socket_unittest.h b/talk/base/socket_unittest.h
new file mode 100644
index 0000000..481f073
--- /dev/null
+++ b/talk/base/socket_unittest.h
@@ -0,0 +1,64 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SOCKET_UNITTEST_H_
+#define TALK_BASE_SOCKET_UNITTEST_H_
+
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// Generic socket tests, to be used when testing individual socketservers.
+// Derive your specific test class from SocketTest, install your
+// socketserver, and call the SocketTest test methods.
+class SocketTest : public testing::Test {
+ protected:
+  SocketTest() : ss_(NULL) {}
+  virtual void SetUp() { ss_ = Thread::Current()->socketserver(); }
+  void TestConnect();
+  void TestConnectWithDnsLookup();
+  void TestConnectFail();
+  void TestConnectWithDnsLookupFail();
+  void TestConnectWithClosedSocket();
+  void TestServerCloseDuringConnect();
+  void TestClientCloseDuringConnect();
+  void TestServerClose();
+  void TestCloseInClosedCallback();
+  void TestSocketServerWait();
+  void TestTcp();
+  void TestSingleFlowControlCallback();
+  void TestUdp();
+  void TestGetSetOptions();
+
+  static const int kTimeout = 5000;  // ms
+  SocketServer* ss_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKET_UNITTEST_H_
diff --git a/talk/base/socketaddress.cc b/talk/base/socketaddress.cc
index cbb0805..a4c7275 100644
--- a/talk/base/socketaddress.cc
+++ b/talk/base/socketaddress.cc
@@ -25,10 +25,15 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "talk/base/socketaddress.h"
+
 #ifdef POSIX
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#if defined(OPENBSD)
+#include <netinet/in_systm.h>
+#endif
 #include <netinet/ip.h>
 #include <arpa/inet.h>
 #include <netdb.h>
@@ -41,22 +46,17 @@
 #include "talk/base/common.h"
 #include "talk/base/logging.h"
 #include "talk/base/nethelpers.h"
-#include "talk/base/socketaddress.h"
 
 #ifdef WIN32
-// Win32 doesn't provide inet_aton, so we add our own version here.
-// Since inet_addr returns 0xFFFFFFFF on error, if we get this value
-// we need to test the input to see if the address really was 255.255.255.255.
-// This is slightly fragile, but better than doing nothing.
-int inet_aton(const char* cp, struct in_addr* inp) {
-  inp->s_addr = inet_addr(cp);
-  return (inp->s_addr == INADDR_NONE &&
-          strcmp(cp, "255.255.255.255") != 0) ? 0 : 1;
-}
-#endif  // WIN32
+#include "talk/base/win32.h"
+#endif
 
 namespace talk_base {
 
+// Address family constants for STUN (see RFC 5389).
+static const int kStunFamilyIPv4 = 1;
+static const int kStunFamilyIPv6 = 2;
+
 SocketAddress::SocketAddress() {
   Clear();
 }
@@ -66,7 +66,12 @@
   SetPort(port);
 }
 
-SocketAddress::SocketAddress(uint32 ip, int port) {
+SocketAddress::SocketAddress(uint32 ip_as_host_order_integer, int port) {
+  SetIP(IPAddress(ip_as_host_order_integer));
+  SetPort(port);
+}
+
+SocketAddress::SocketAddress(const IPAddress& ip, int port) {
   SetIP(ip);
   SetPort(port);
 }
@@ -77,36 +82,52 @@
 
 void SocketAddress::Clear() {
   hostname_.clear();
-  ip_ = 0;
+  literal_ = false;
+  ip_ = IPAddress(INADDR_ANY);
   port_ = 0;
 }
 
 bool SocketAddress::IsNil() const {
-  return hostname_.empty() && (0 == ip_) && (0 == port_);
+  return hostname_.empty() && IPIsAny(ip_) && 0 == port_;
 }
 
 bool SocketAddress::IsComplete() const {
-  return (0 != ip_) && (0 != port_);
+  return (!IPIsAny(ip_)) && (0 != port_);
 }
 
 SocketAddress& SocketAddress::operator=(const SocketAddress& addr) {
   hostname_ = addr.hostname_;
   ip_ = addr.ip_;
   port_ = addr.port_;
+  literal_ = addr.literal_;
   return *this;
 }
 
-void SocketAddress::SetIP(uint32 ip) {
+void SocketAddress::SetIP(uint32 ip_as_host_order_integer) {
   hostname_.clear();
+  literal_ = false;
+  ip_ = IPAddress(ip_as_host_order_integer);
+}
+
+void SocketAddress::SetIP(const IPAddress& ip) {
+  hostname_.clear();
+  literal_ = false;
   ip_ = ip;
 }
 
 void SocketAddress::SetIP(const std::string& hostname) {
   hostname_ = hostname;
-  ip_ = StringToIP(hostname);
+  literal_ = IPFromString(hostname, &ip_);
+  if (!literal_) {
+    ip_ = IPAddress(INADDR_ANY);
+  }
 }
 
-void SocketAddress::SetResolvedIP(uint32 ip) {
+void SocketAddress::SetResolvedIP(uint32 ip_as_host_order_integer) {
+  ip_ = IPAddress(ip_as_host_order_integer);
+}
+
+void SocketAddress::SetResolvedIP(const IPAddress& ip) {
   ip_ = ip;
 }
 
@@ -116,6 +137,10 @@
 }
 
 uint32 SocketAddress::ip() const {
+  return ip_.v4AddressAsHostOrderInteger();
+}
+
+const IPAddress& SocketAddress::ipaddr() const {
   return ip_;
 }
 
@@ -124,9 +149,15 @@
 }
 
 std::string SocketAddress::IPAsString() const {
-  if (!hostname_.empty())
+  // If the hostname was a literal IP string, it may need to have square
+  // brackets added (for SocketAddress::ToString()).
+  if (!literal_ && !hostname_.empty())
     return hostname_;
-  return IPToString(ip_);
+  if (ip_.family() == AF_INET6) {
+    return "[" + ip_.ToString() + "]";
+  } else {
+    return ip_.ToString();
+  }
 }
 
 std::string SocketAddress::PortAsString() const {
@@ -144,11 +175,24 @@
 }
 
 bool SocketAddress::FromString(const std::string& str) {
-  std::string::size_type pos = str.find(':');
-  if (std::string::npos == pos)
-    return false;
-  SetPort(strtoul(str.substr(pos + 1).c_str(), NULL, 10));
-  SetIP(str.substr(0, pos));
+  if (str.at(0) == '[') {
+    std::string::size_type closebracket = str.rfind(']');
+    if (closebracket != std::string::npos) {
+      std::string::size_type colon = str.find(':', closebracket);
+      if (colon != std::string::npos && colon > closebracket) {
+        SetPort(strtoul(str.substr(colon + 1).c_str(), NULL, 10));
+        SetIP(str.substr(1, closebracket - 1));
+      } else {
+        return false;
+      }
+    }
+  } else {
+    std::string::size_type pos = str.find(':');
+    if (std::string::npos == pos)
+      return false;
+    SetPort(strtoul(str.substr(pos + 1).c_str(), NULL, 10));
+    SetIP(str.substr(0, pos));
+  }
   return true;
 }
 
@@ -158,28 +202,25 @@
 }
 
 bool SocketAddress::IsAnyIP() const {
-  return (ip_ == 0);
+  return IPIsAny(ip_);
 }
 
 bool SocketAddress::IsLoopbackIP() const {
-  if (0 == ip_) {
-    return (0 == stricmp(hostname_.c_str(), "localhost"));
-  } else {
-    return ((ip_ >> 24) == 127);
-  }
+  return IPIsLoopback(ip_) || (IPIsAny(ip_) &&
+                               0 == strcmp(hostname_.c_str(), "localhost"));
 }
 
 bool SocketAddress::IsLocalIP() const {
   if (IsLoopbackIP())
     return true;
 
-  std::vector<uint32> ips;
-  if (0 == ip_) {
+  std::vector<IPAddress> ips;
+  if (IPIsAny(ip_)) {
     if (!hostname_.empty()
         && (0 == stricmp(hostname_.c_str(), GetHostname().c_str()))) {
       return true;
     }
-  } else if (GetLocalIPs(ips)) {
+  } else if (GetLocalIPs(&ips)) {
     for (size_t i = 0; i < ips.size(); ++i) {
       if (ips[i] == ip_) {
         return true;
@@ -190,15 +231,11 @@
 }
 
 bool SocketAddress::IsPrivateIP() const {
-  return ((ip_ >> 24) == 127) ||
-         ((ip_ >> 24) == 10) ||
-         ((ip_ >> 20) == ((172 << 4) | 1)) ||
-         ((ip_ >> 16) == ((192 << 8) | 168)) ||
-         ((ip_ >> 16) == ((169 << 8) | 254));
+  return IPIsPrivate(ip_);
 }
 
 bool SocketAddress::IsUnresolvedIP() const {
-  return IsAny() && !hostname_.empty();
+  return IsAny() && !literal_ && !hostname_.empty();
 }
 
 bool SocketAddress::ResolveIP(bool force, int* error) {
@@ -210,9 +247,10 @@
     LOG_F(LS_VERBOSE) << "(" << hostname_ << ")";
     int errcode = 0;
     if (hostent* pHost = SafeGetHostByName(hostname_.c_str(), &errcode)) {
-      ip_ = NetworkToHost32(*reinterpret_cast<uint32*>(pHost->h_addr_list[0]));
-      LOG_F(LS_VERBOSE) << "(" << hostname_ << ") resolved to: "
-                        << IPToString(ip_);
+      if (IPFromHostEnt(pHost, &ip_)) {
+        LOG_F(LS_VERBOSE) << "(" << hostname_ << ") resolved to: "
+                          << ip_.ToString();
+      }
       FreeHostEnt(pHost);
     } else {
       LOG_F(LS_ERROR) << "(" << hostname_ << ") err: " << errcode;
@@ -221,7 +259,7 @@
       *error = errcode;
     }
   }
-  return (ip_ != 0);
+  return (!IPIsAny(ip_));
 }
 
 bool SocketAddress::operator==(const SocketAddress& addr) const {
@@ -235,7 +273,7 @@
     return false;
 
   // We only check hostnames if both IPs are zero.  This matches EqualIPs()
-  if (addr.ip_ == 0) {
+  if (addr.IsAnyIP()) {
     if (hostname_ < addr.hostname_)
       return true;
     else if (addr.hostname_ < hostname_)
@@ -246,7 +284,8 @@
 }
 
 bool SocketAddress::EqualIPs(const SocketAddress& addr) const {
-  return (ip_ == addr.ip_) && ((ip_ != 0) || (hostname_ == addr.hostname_));
+  return (ip_ == addr.ip_) &&
+      ((!IPIsAny(ip_)) || (hostname_ == addr.hostname_));
 }
 
 bool SocketAddress::EqualPorts(const SocketAddress& addr) const {
@@ -255,41 +294,23 @@
 
 size_t SocketAddress::Hash() const {
   size_t h = 0;
-  h ^= ip_;
+  h ^= HashIP(ip_);
   h ^= port_ | (port_ << 16);
   return h;
 }
 
-size_t SocketAddress::Size_() const {
-  return sizeof(ip_) + sizeof(port_) + 2;
-}
-
-bool SocketAddress::Write_(char* buf, int len) const {
-  if (len < static_cast<int>(Size_()))
-    return false;
-  buf[0] = 0;
-  buf[1] = AF_INET;
-  SetBE16(buf + 2, port_);
-  SetBE32(buf + 4, ip_);
-  return true;
-}
-
-bool SocketAddress::Read_(const char* buf, int len) {
-  if (len < static_cast<int>(Size_()) || buf[1] != AF_INET)
-    return false;
-  port_ = GetBE16(buf + 2);
-  ip_ = GetBE32(buf + 4);
-  return true;
-}
-
 void SocketAddress::ToSockAddr(sockaddr_in* saddr) const {
   memset(saddr, 0, sizeof(*saddr));
+  if (ip_.family() != AF_INET) {
+    saddr->sin_family = AF_UNSPEC;
+    return;
+  }
   saddr->sin_family = AF_INET;
   saddr->sin_port = HostToNetwork16(port_);
-  if (0 == ip_) {
+  if (IPIsAny(ip_)) {
     saddr->sin_addr.s_addr = INADDR_ANY;
   } else {
-    saddr->sin_addr.s_addr = HostToNetwork32(ip_);
+    saddr->sin_addr = ip_.ipv4_address();
   }
 }
 
@@ -298,29 +319,76 @@
     return false;
   SetIP(NetworkToHost32(saddr.sin_addr.s_addr));
   SetPort(NetworkToHost16(saddr.sin_port));
+  literal_ = false;
   return true;
 }
 
-std::string SocketAddress::IPToString(uint32 ip) {
+static size_t ToSockAddrStorageHelper(sockaddr_storage* addr,
+                                      IPAddress ip, int port, int scope_id) {
+  memset(addr, 0, sizeof(sockaddr_storage));
+  addr->ss_family = ip.family();
+  if (addr->ss_family == AF_INET6) {
+    sockaddr_in6* saddr = reinterpret_cast<sockaddr_in6*>(addr);
+    saddr->sin6_addr = ip.ipv6_address();
+    saddr->sin6_port = HostToNetwork16(port);
+    saddr->sin6_scope_id = scope_id;
+    return sizeof(sockaddr_in6);
+  } else if (addr->ss_family == AF_INET) {
+    sockaddr_in* saddr = reinterpret_cast<sockaddr_in*>(addr);
+    saddr->sin_addr = ip.ipv4_address();
+    saddr->sin_port = HostToNetwork16(port);
+    return sizeof(sockaddr_in);
+  }
+  return 0;
+}
+
+size_t SocketAddress::ToDualStackSockAddrStorage(sockaddr_storage *addr) const {
+  return ToSockAddrStorageHelper(addr, ip_.AsIPv6Address(), port_, scope_id_);
+}
+
+size_t SocketAddress::ToSockAddrStorage(sockaddr_storage* addr) const {
+  return ToSockAddrStorageHelper(addr, ip_, port_, scope_id_);
+}
+
+std::string SocketAddress::IPToString(uint32 ip_as_host_order_integer) {
   std::ostringstream ost;
-  ost << ((ip >> 24) & 0xff);
+  ost << ((ip_as_host_order_integer >> 24) & 0xff);
   ost << '.';
-  ost << ((ip >> 16) & 0xff);
+  ost << ((ip_as_host_order_integer >> 16) & 0xff);
   ost << '.';
-  ost << ((ip >> 8) & 0xff);
+  ost << ((ip_as_host_order_integer >> 8) & 0xff);
   ost << '.';
-  ost << ((ip >> 0) & 0xff);
+  ost << ((ip_as_host_order_integer >> 0) & 0xff);
   return ost.str();
 }
 
 bool SocketAddress::StringToIP(const std::string& hostname, uint32* ip) {
   in_addr addr;
-  if (inet_aton(hostname.c_str(), &addr) == 0)
+  if (talk_base::inet_pton(AF_INET, hostname.c_str(), &addr) == 0)
     return false;
   *ip = NetworkToHost32(addr.s_addr);
   return true;
 }
 
+bool SocketAddress::StringToIP(const std::string& hostname, IPAddress* ip) {
+  in_addr addr4;
+  if (talk_base::inet_pton(AF_INET, hostname.c_str(), &addr4) > 0) {
+    if (ip) {
+      *ip = IPAddress(addr4);
+    }
+    return true;
+  }
+
+  in6_addr addr6;
+  if (talk_base::inet_pton(AF_INET6, hostname.c_str(), &addr6) > 0) {
+    if (ip) {
+      *ip = IPAddress(addr6);
+    }
+    return true;
+  }
+  return false;
+}
+
 uint32 SocketAddress::StringToIP(const std::string& hostname) {
   uint32 ip = 0;
   StringToIP(hostname, &ip);
@@ -334,25 +402,48 @@
   return "";
 }
 
-bool SocketAddress::GetLocalIPs(std::vector<uint32>& ips) {
-  ips.clear();
+bool SocketAddress::GetLocalIPs(std::vector<IPAddress>* ips) {
+  if (!ips) {
+    return false;
+  }
+  ips->clear();
 
-  const std::string hostname = GetHostname();
+  const std::string hostname = SocketAddress::GetHostname();
   if (hostname.empty())
     return false;
 
   int errcode;
   if (hostent* pHost = SafeGetHostByName(hostname.c_str(), &errcode)) {
     for (size_t i = 0; pHost->h_addr_list[i]; ++i) {
-      uint32 ip =
-        NetworkToHost32(*reinterpret_cast<uint32 *>(pHost->h_addr_list[i]));
-      ips.push_back(ip);
+      IPAddress ip;
+      if (IPFromHostEnt(pHost, i, &ip)) {
+        ips->push_back(ip);
+      }
     }
     FreeHostEnt(pHost);
-    return !ips.empty();
+    return !ips->empty();
   }
   LOG(LS_ERROR) << "gethostbyname err: " << errcode;
   return false;
 }
 
+bool SocketAddressFromSockAddrStorage(const sockaddr_storage& addr,
+                                      SocketAddress* out) {
+  if (!out) {
+    return false;
+  }
+  if (addr.ss_family == AF_INET) {
+    const sockaddr_in* saddr = reinterpret_cast<const sockaddr_in*>(&addr);
+    *out = SocketAddress(IPAddress(saddr->sin_addr),
+                         NetworkToHost16(saddr->sin_port));
+    return true;
+  } else if (addr.ss_family == AF_INET6) {
+    const sockaddr_in6* saddr = reinterpret_cast<const sockaddr_in6*>(&addr);
+    *out = SocketAddress(IPAddress(saddr->sin6_addr),
+                         NetworkToHost16(saddr->sin6_port));
+    out->SetScopeID(saddr->sin6_scope_id);
+    return true;
+  }
+  return false;
+}
 }  // namespace talk_base
diff --git a/talk/base/socketaddress.h b/talk/base/socketaddress.h
index 4af80c9..9bd1fad 100644
--- a/talk/base/socketaddress.h
+++ b/talk/base/socketaddress.h
@@ -32,28 +32,31 @@
 #include <vector>
 #include <iosfwd>
 #include "talk/base/basictypes.h"
+#include "talk/base/ipaddress.h"
+
 #undef SetPort
 
 struct sockaddr_in;
+struct sockaddr_storage;
 
 namespace talk_base {
 
-// Records an IP address and port, which are 32 and 16 bit integers,
-// respectively, both in <b>host byte-order</b>.
+// Records an IP address and port.
 class SocketAddress {
  public:
   // Creates a nil address.
   SocketAddress();
 
-  // Creates the address with the given host and port.  If use_dns is true,
-  // the hostname will be immediately resolved to an IP (which may block for
-  // several seconds if DNS is not available).  Alternately, set use_dns to
-  // false, and then call Resolve() to complete resolution later, or use
-  // SetResolvedIP to set the IP explictly.
+  // Creates the address with the given host and port. Host may be a
+  // literal IP string or a hostname to be resolved later with ResolveIP().
   SocketAddress(const std::string& hostname, int port);
 
   // Creates the address with the given IP and port.
-  SocketAddress(uint32 ip, int port);
+  // IP is given as an integer in host byte order. V4 only, to be deprecated.
+  SocketAddress(uint32 ip_as_host_order_integer, int port);
+
+  // Creates the address with the given IP and port.
+  SocketAddress(const IPAddress& ip, int port);
 
   // Creates a copy of the given address.
   SocketAddress(const SocketAddress& addr);
@@ -70,8 +73,12 @@
   // Replaces our address with the given one.
   SocketAddress& operator=(const SocketAddress& addr);
 
+  // Changes the IP of this address to the given one, and clears the hostname
+  // IP is given as an integer in host byte order. V4 only, to be deprecated..
+  void SetIP(uint32 ip_as_host_order_integer);
+
   // Changes the IP of this address to the given one, and clears the hostname.
-  void SetIP(uint32 ip);
+  void SetIP(const IPAddress& ip);
 
   // Changes the hostname of this address to the given one.
   // Does not resolve the address; use Resolve to do so.
@@ -79,41 +86,59 @@
 
   // Sets the IP address while retaining the hostname.  Useful for bypassing
   // DNS for a pre-resolved IP.
-  void SetResolvedIP(uint32 ip);
+  // IP is given as an integer in host byte order. V4 only, to be deprecated.
+  void SetResolvedIP(uint32 ip_as_host_order_integer);
+
+  // Sets the IP address while retaining the hostname.  Useful for bypassing
+  // DNS for a pre-resolved IP.
+  void SetResolvedIP(const IPAddress& ip);
 
   // Changes the port of this address to the given one.
   void SetPort(int port);
 
-  // Returns the hostname
+  // Returns the hostname.
   const std::string& hostname() const { return hostname_; }
 
-  // Returns the IP address.
+  // Returns the IP address as a host byte order integer.
+  // Returns 0 for non-v4 addresses.
   uint32 ip() const;
 
+  const IPAddress& ipaddr() const;
+
   // Returns the port part of this address.
   uint16 port() const;
 
-  // Returns the IP address in dotted form.
+  // Returns the scope ID associated with this address. Scope IDs are a
+  // necessary addition to IPv6 link-local addresses, with different network
+  // interfaces having different scope-ids for their link-local addresses.
+  // IPv4 address do not have scope_ids and sockaddr_in structures do not have
+  // a field for them.
+  int scope_id() const {return scope_id_; }
+  void SetScopeID(int id) { scope_id_ = id; }
+
+  // Returns the IP address (or hostname) in printable form.
   std::string IPAsString() const;
 
-  // Returns the port as a string
+  // Returns the port as a string.
   std::string PortAsString() const;
 
-  // Returns hostname:port
+  // Returns hostname:port or [hostname]:port.
   std::string ToString() const;
 
-  // Parses hostname:port
+  // Parses hostname:port and [hostname]:port.
   bool FromString(const std::string& str);
 
   friend std::ostream& operator<<(std::ostream& os, const SocketAddress& addr);
 
-  // Determines whether this represents a missing / any IP address.  Hostname
-  // and/or port may be set.
+  // Determines whether this represents a missing / any IP address.
+  // That is, 0.0.0.0 or ::.
+  // Hostname and/or port may be set.
   bool IsAnyIP() const;
   inline bool IsAny() const { return IsAnyIP(); }  // deprecated
 
-  // Determines whether the IP address refers to a loopback address, i.e. within
-  // the range 127.0.0.0/8.
+  // Determines whether the IP address refers to a loopback address.
+  // For v4 addresses this means the address is in the range 127.0.0.0/8.
+  // For v6 addresses this means the address is ::1.
   bool IsLoopbackIP() const;
 
   // Determines wither the IP address refers to any adapter on the local
@@ -121,7 +146,8 @@
   bool IsLocalIP() const;
 
   // Determines whether the IP address is in one of the private ranges:
-  // 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12.
+  // For v4: 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12.
+  // For v6: FE80::/16 and ::1.
   bool IsPrivateIP() const;
 
   // Determines whether the hostname has been resolved to an IP.
@@ -131,6 +157,7 @@
   // Attempt to resolve a hostname to IP address.
   // Returns false if resolution is required but failed, and sets error.
   // 'force' will cause re-resolution of hostname.
+  // TODO: Deprecate this function.
   bool ResolveIP(bool force = false, int* error = NULL);
 
   // Determines whether this address is identical to the given one.
@@ -151,41 +178,54 @@
   // Hashes this address into a small number.
   size_t Hash() const;
 
-  // Returns the size of this address when written.
-  size_t Size_() const;
-
-  // Writes this address into the given buffer, according to RFC 3489.
-  bool Write_(char* buf, int len) const;
-
-  // Reads this address from the given buffer, according to RFC 3489.
-  bool Read_(const char* buf, int len);
-
   // Write this address to a sockaddr_in.
+  // If IPv6, will zero out the sockaddr_in and sets family to AF_UNSPEC.
   void ToSockAddr(sockaddr_in* saddr) const;
 
   // Read this address from a sockaddr_in.
   bool FromSockAddr(const sockaddr_in& saddr);
 
-  // Converts the IP address given in compact form into dotted form.
-  static std::string IPToString(uint32 ip);
+  // Read and write the address to/from a sockaddr_storage.
+  // Dual stack version always sets family to AF_INET6, and maps v4 addresses.
+  // The other version doesn't map, and outputs an AF_INET address for
+  // v4 or mapped addresses, and AF_INET6 addresses for others.
+  // Returns the size of the sockaddr_in or sockaddr_in6 structure that is
+  // written to the sockaddr_storage, or zero on failure.
+  size_t ToDualStackSockAddrStorage(sockaddr_storage* saddr) const;
+  size_t ToSockAddrStorage(sockaddr_storage* saddr) const;
+
+  // Converts the IP address given in 'compact form' into dotted form.
+  // IP is given as an integer in host byte order. V4 only, to be deprecated.
+  // TODO: Deprecate this.
+  static std::string IPToString(uint32 ip_as_host_order_integer);
 
   // Converts the IP address given in dotted form into compact form.
-  // Only dotted names (A.B.C.D) are resolved.
+  // Only dotted names (A.B.C.D) are  converted.
+  // Output integer is returned in host byte order.
+  // TODO: Deprecate, replace wth agnostic versions.
   static bool StringToIP(const std::string& str, uint32* ip);
-  static uint32 StringToIP(const std::string& str);  // deprecated
+  static uint32 StringToIP(const std::string& str);
 
-  // Get local machine's hostname
-  static std::string GetHostname();
+  // Converts the IP address given in printable form into an IPAddress.
+  static bool StringToIP(const std::string& str, IPAddress* ip);
 
-  // Get a list of the local machine's ip addresses
-  static bool GetLocalIPs(std::vector<uint32>& ips);
+  // Get a list of the local machine's ip addresses.
+  // TODO: Move to nethelpers or similar (doesn't belong in socketaddress).
+  static bool GetLocalIPs(std::vector<IPAddress>* ips);
 
  private:
+  // Get local machine's hostname.
+  static std::string GetHostname();
   std::string hostname_;
-  uint32 ip_;
+  IPAddress ip_;
   uint16 port_;
+  int scope_id_;
+  bool literal_;  // Indicates that 'hostname_' contains a literal IP string.
 };
 
+bool SocketAddressFromSockAddrStorage(const sockaddr_storage& saddr,
+                                      SocketAddress* out);
+
 }  // namespace talk_base
 
 #endif  // TALK_BASE_SOCKETADDRESS_H_
diff --git a/talk/base/socketaddress_unittest.cc b/talk/base/socketaddress_unittest.cc
index 95b0071..5ef05b5 100644
--- a/talk/base/socketaddress_unittest.cc
+++ b/talk/base/socketaddress_unittest.cc
@@ -31,41 +31,65 @@
 
 #include "talk/base/gunit.h"
 #include "talk/base/socketaddress.h"
+#include "talk/base/ipaddress.h"
 
 namespace talk_base {
 
+const in6_addr kTestV6Addr =  { { {0x20, 0x01, 0x0d, 0xb8,
+                                   0x10, 0x20, 0x30, 0x40,
+                                   0x50, 0x60, 0x70, 0x80,
+                                   0x90, 0xA0, 0xB0, 0xC0} } };
+const in6_addr kMappedV4Addr = { { {0x00, 0x00, 0x00, 0x00,
+                                    0x00, 0x00, 0x00, 0x00,
+                                    0x00, 0x00, 0xFF, 0xFF,
+                                    0x01, 0x02, 0x03, 0x04} } };
+const std::string kTestV6AddrString = "2001:db8:1020:3040:5060:7080:90a0:b0c0";
+const std::string kTestV6AddrFullString =
+    "[2001:db8:1020:3040:5060:7080:90a0:b0c0]:5678";
+
 TEST(SocketAddressTest, TestDefaultCtor) {
   SocketAddress addr;
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0U, addr.ip());
+  EXPECT_EQ(IPAddress(INADDR_ANY), addr.ipaddr());
   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);
+  SocketAddress addr(IPAddress(0x01020304), 5678);
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("", addr.hostname());
   EXPECT_EQ("1.2.3.4:5678", addr.ToString());
 }
 
-TEST(SocketAddressTest, TestStringPortCtor) {
+TEST(SocketAddressTest, TestIPv4StringPortCtor) {
   SocketAddress addr("1.2.3.4", 5678);
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("1.2.3.4", addr.hostname());
   EXPECT_EQ("1.2.3.4:5678", addr.ToString());
 }
 
+TEST(SocketAddressTest, TestIPv6StringPortCtor) {
+  SocketAddress addr2(kTestV6AddrString, 1234);
+  IPAddress tocheck(kTestV6Addr);
+
+  EXPECT_FALSE(addr2.IsUnresolvedIP());
+  EXPECT_EQ(tocheck, addr2.ipaddr());
+  EXPECT_EQ(1234, addr2.port());
+  EXPECT_EQ(kTestV6AddrString, addr2.hostname());
+  EXPECT_EQ("[" + kTestV6AddrString + "]:1234", addr2.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(IPAddress(0xFFFFFFFFU), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("255.255.255.255", addr.hostname());
   EXPECT_EQ("255.255.255.255:5678", addr.ToString());
@@ -74,7 +98,7 @@
 TEST(SocketAddressTest, TestHostnamePortCtor) {
   SocketAddress addr("a.b.com", 5678);
   EXPECT_TRUE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0U, addr.ip());
+  EXPECT_EQ(IPAddress(INADDR_ANY), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("a.b.com", addr.hostname());
   EXPECT_EQ("a.b.com:5678", addr.ToString());
@@ -84,7 +108,7 @@
   SocketAddress from("1.2.3.4", 5678);
   SocketAddress addr(from);
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("1.2.3.4", addr.hostname());
   EXPECT_EQ("1.2.3.4:5678", addr.ToString());
@@ -92,68 +116,77 @@
 
 TEST(SocketAddressTest, TestAssign) {
   SocketAddress from("1.2.3.4", 5678);
-  SocketAddress addr(0x88888888, 9999);
+  SocketAddress addr(IPAddress(0x88888888), 9999);
   addr = from;
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
   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);
+  SocketAddress addr(IPAddress(0x88888888), 9999);
+  addr.SetIP(IPAddress(0x01020304));
   addr.SetPort(5678);
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
   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);
+  SocketAddress addr(IPAddress(0x88888888), 9999);
   addr.SetIP("1.2.3.4");
   addr.SetPort(5678);
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
   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);
+  SocketAddress addr(IPAddress(0x88888888), 9999);
   addr.SetIP("a.b.com");
   addr.SetPort(5678);
   EXPECT_TRUE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0U, addr.ip());
+  EXPECT_EQ(IPAddress(INADDR_ANY), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("a.b.com", addr.hostname());
   EXPECT_EQ("a.b.com:5678", addr.ToString());
-  addr.SetResolvedIP(0x01020304);
+  addr.SetResolvedIP(IPAddress(0x01020304));
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
   EXPECT_EQ("a.b.com", addr.hostname());
   EXPECT_EQ("a.b.com:5678", addr.ToString());
 }
 
-TEST(SocketAddressTest, TestFromString) {
+TEST(SocketAddressTest, TestFromIPv4String) {
   SocketAddress addr;
   EXPECT_TRUE(addr.FromString("1.2.3.4:5678"));
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("1.2.3.4", addr.hostname());
   EXPECT_EQ("1.2.3.4:5678", addr.ToString());
 }
 
+TEST(SocketAddressTest, TestFromIPv6String) {
+  SocketAddress addr;
+  EXPECT_TRUE(addr.FromString(kTestV6AddrFullString));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ(kTestV6AddrString, addr.hostname());
+  EXPECT_EQ(kTestV6AddrFullString, 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(IPAddress(INADDR_ANY), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("a.b.com", addr.hostname());
   EXPECT_EQ("a.b.com:5678", addr.ToString());
@@ -165,22 +198,61 @@
   from.ToSockAddr(&addr_in);
   EXPECT_TRUE(addr.FromSockAddr(addr_in));
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("", addr.hostname());
   EXPECT_EQ("1.2.3.4:5678", addr.ToString());
 }
 
-TEST(SocketAddressTest, TestToFromBuffer) {
+TEST(SocketAddressTest, TestToFromSockAddrStorage) {
   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)));
+  sockaddr_storage addr_storage;
+  from.ToSockAddrStorage(&addr_storage);
+  EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
   EXPECT_FALSE(addr.IsUnresolvedIP());
-  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("", addr.hostname());
   EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+
+  addr.Clear();
+  from.ToDualStackSockAddrStorage(&addr_storage);
+  EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(IPAddress(kMappedV4Addr), addr.ipaddr());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ("[::ffff:1.2.3.4]:5678", addr.ToString());
+
+  addr.Clear();
+  memset(&addr_storage, 0, sizeof(sockaddr_storage));
+  from = SocketAddress(kTestV6AddrString, 5678);
+  from.SetScopeID(6);
+  from.ToSockAddrStorage(&addr_storage);
+  EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ(kTestV6AddrFullString, addr.ToString());
+  EXPECT_EQ(6, addr.scope_id());
+
+  addr.Clear();
+  from.ToDualStackSockAddrStorage(&addr_storage);
+  EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ(kTestV6AddrFullString, addr.ToString());
+  EXPECT_EQ(6, addr.scope_id());
+
+  addr = from;
+  addr_storage.ss_family = AF_UNSPEC;
+  EXPECT_FALSE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+  EXPECT_EQ(from, addr);
+
+  EXPECT_FALSE(SocketAddressFromSockAddrStorage(addr_storage, NULL));
 }
 
 TEST(SocketAddressTest, TestGoodResolve) {
@@ -191,7 +263,7 @@
   EXPECT_EQ(0, error);
   EXPECT_FALSE(addr.IsUnresolvedIP());
   EXPECT_TRUE(addr.IsLoopbackIP());
-  EXPECT_EQ(0x7F000001U, addr.ip());
+  EXPECT_EQ(IPAddress(INADDR_LOOPBACK), addr.ipaddr());
   EXPECT_EQ(5678, addr.port());
   EXPECT_EQ("localhost", addr.hostname());
   EXPECT_EQ("localhost:5678", addr.ToString());
@@ -206,4 +278,74 @@
   EXPECT_TRUE(addr.IsUnresolvedIP());
 }
 
+bool AreEqual(const SocketAddress& addr1,
+              const SocketAddress& addr2) {
+  return addr1 == addr2 && addr2 == addr1 &&
+      !(addr1 != addr2) && !(addr2 != addr1);
+}
+
+bool AreUnequal(const SocketAddress& addr1,
+                const SocketAddress& addr2) {
+  return !(addr1 == addr2) && !(addr2 == addr1) &&
+      addr1 != addr2 && addr2 != addr1;
+}
+
+TEST(SocketAddressTest, TestEqualityOperators) {
+  SocketAddress addr1("1.2.3.4", 5678);
+  SocketAddress addr2("1.2.3.4", 5678);
+  EXPECT_PRED2(AreEqual, addr1, addr2);
+
+  addr2 = SocketAddress("0.0.0.1", 5678);
+  EXPECT_PRED2(AreUnequal, addr1, addr2);
+
+  addr2 = SocketAddress("1.2.3.4", 1234);
+  EXPECT_PRED2(AreUnequal, addr1, addr2);
+
+  addr2 = SocketAddress(kTestV6AddrString, 5678);
+  EXPECT_PRED2(AreUnequal, addr1, addr2);
+
+  addr1 = SocketAddress(kTestV6AddrString, 5678);
+  EXPECT_PRED2(AreEqual, addr1, addr2);
+
+  addr2 = SocketAddress(kTestV6AddrString, 1234);
+  EXPECT_PRED2(AreUnequal, addr1, addr2);
+
+  addr2 = SocketAddress("fe80::1", 5678);
+  EXPECT_PRED2(AreUnequal, addr1, addr2);
+}
+
+bool IsLessThan(const SocketAddress& addr1,
+                                      const SocketAddress& addr2) {
+  return addr1 < addr2 &&
+      !(addr2 < addr1) &&
+      !(addr1 == addr2);
+}
+
+TEST(SocketAddressTest, TestComparisonOperator) {
+  SocketAddress addr1("1.2.3.4", 5678);
+  SocketAddress addr2("1.2.3.4", 5678);
+
+  EXPECT_FALSE(addr1 < addr2);
+  EXPECT_FALSE(addr2 < addr1);
+
+  addr2 = SocketAddress("1.2.3.4", 5679);
+  EXPECT_PRED2(IsLessThan, addr1, addr2);
+
+  addr2 = SocketAddress("2.2.3.4", 49152);
+  EXPECT_PRED2(IsLessThan, addr1, addr2);
+
+  addr2 = SocketAddress(kTestV6AddrString, 5678);
+  EXPECT_PRED2(IsLessThan, addr1, addr2);
+
+  addr1 = SocketAddress("fe80::1", 5678);
+  EXPECT_PRED2(IsLessThan, addr2, addr1);
+
+  addr2 = SocketAddress("fe80::1", 5679);
+  EXPECT_PRED2(IsLessThan, addr1, addr2);
+
+  addr2 = SocketAddress("fe80::1", 5678);
+  EXPECT_FALSE(addr1 < addr2);
+  EXPECT_FALSE(addr2 < addr1);
+}
+
 }  // namespace talk_base
diff --git a/talk/base/ssladapter.h b/talk/base/ssladapter.h
index 687cc24..1583dc2 100644
--- a/talk/base/ssladapter.h
+++ b/talk/base/ssladapter.h
@@ -25,8 +25,8 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef TALK_BASE_SSLADAPTER_H__
-#define TALK_BASE_SSLADAPTER_H__
+#ifndef TALK_BASE_SSLADAPTER_H_
+#define TALK_BASE_SSLADAPTER_H_
 
 #include "talk/base/asyncsocket.h"
 
@@ -35,8 +35,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 class SSLAdapter : public AsyncSocketAdapter {
-public:
-  SSLAdapter(AsyncSocket* socket)
+ public:
+  explicit SSLAdapter(AsyncSocket* socket)
     : AsyncSocketAdapter(socket), ignore_bad_cert_(false) { }
 
   bool ignore_bad_cert() const { return ignore_bad_cert_; }
@@ -50,7 +50,7 @@
   // Create the default SSL adapter for this platform
   static SSLAdapter* Create(AsyncSocket* socket);
 
-private:
+ private:
   // If true, the server certificate need not match the configured hostname.
   bool ignore_bad_cert_;
 };
@@ -73,4 +73,4 @@
 
 }  // namespace talk_base
 
-#endif // TALK_BASE_SSLADAPTER_H__
+#endif  // TALK_BASE_SSLADAPTER_H_
diff --git a/talk/base/sslidentity.cc b/talk/base/sslidentity.cc
index 665e700..0cb52f1 100644
--- a/talk/base/sslidentity.cc
+++ b/talk/base/sslidentity.cc
@@ -54,6 +54,13 @@
 
 namespace talk_base {
 
+// From RFC 4572
+const char DIGEST_SHA_1[] = "sha-1";
+const char DIGEST_SHA_224[] = "sha-224";
+const char DIGEST_SHA_256[] = "sha-256";
+const char DIGEST_SHA_384[] = "sha-384";
+const char DIGEST_SHA_512[] = "sha-512";
+
 SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string,
                                               int* pem_length) {
   return OpenSSLCertificate::FromPEMString(pem_string, pem_length);
diff --git a/talk/base/sslidentity.h b/talk/base/sslidentity.h
index ed996b4..37e3080 100644
--- a/talk/base/sslidentity.h
+++ b/talk/base/sslidentity.h
@@ -34,6 +34,13 @@
 
 namespace talk_base {
 
+// Definitions for the digest algorithms
+extern const char DIGEST_SHA_1[];
+extern const char DIGEST_SHA_224[];
+extern const char DIGEST_SHA_256[];
+extern const char DIGEST_SHA_384[];
+extern const char DIGEST_SHA_512[];
+
 // Abstract interface overridden by SSL library specific
 // implementations.
 
@@ -61,6 +68,11 @@
 
   // Returns a PEM encoded string representation of the certificate.
   virtual std::string ToPEMString() const = 0;
+
+  // Compute the digest of the certificate given algorithm
+  virtual bool ComputeDigest(const std::string &algorithm,
+                             unsigned char *digest, std::size_t size,
+                             std::size_t *length) const = 0;
 };
 
 // Our identity in an SSL negotiation: a keypair and certificate (both
diff --git a/talk/base/sslidentity_unittest.cc b/talk/base/sslidentity_unittest.cc
new file mode 100644
index 0000000..b6200cd
--- /dev/null
+++ b/talk/base/sslidentity_unittest.cc
@@ -0,0 +1,143 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ * Portions Copyright 2011, RTFM, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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/sslidentity.h"
+
+const char kTestCertificate[] = "-----BEGIN CERTIFICATE-----\n"
+    "MIIB6TCCAVICAQYwDQYJKoZIhvcNAQEEBQAwWzELMAkGA1UEBhMCQVUxEzARBgNV\n"
+    "BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMRswGQYD\n"
+    "VQQDExJUZXN0IENBICgxMDI0IGJpdCkwHhcNMDAxMDE2MjIzMTAzWhcNMDMwMTE0\n"
+    "MjIzMTAzWjBjMQswCQYDVQQGEwJBVTETMBEGA1UECBMKUXVlZW5zbGFuZDEaMBgG\n"
+    "A1UEChMRQ3J5cHRTb2Z0IFB0eSBMdGQxIzAhBgNVBAMTGlNlcnZlciB0ZXN0IGNl\n"
+    "cnQgKDUxMiBiaXQpMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ+zw4Qnlf8SMVIP\n"
+    "Fe9GEcStgOY2Ww/dgNdhjeD8ckUJNP5VZkVDTGiXav6ooKXfX3j/7tdkuD8Ey2//\n"
+    "Kv7+ue0CAwEAATANBgkqhkiG9w0BAQQFAAOBgQCT0grFQeZaqYb5EYfk20XixZV4\n"
+    "GmyAbXMftG1Eo7qGiMhYzRwGNWxEYojf5PZkYZXvSqZ/ZXHXa4g59jK/rJNnaVGM\n"
+    "k+xIX8mxQvlV0n5O9PIha5BX5teZnkHKgL8aKKLKW1BK7YTngsfSzzaeame5iKfz\n"
+    "itAE+OjGF+PFKbwX8Q==\n"
+    "-----END CERTIFICATE-----\n";
+
+const unsigned char kTestCertSha1[] = {0xA6, 0xC8, 0x59, 0xEA,
+                                       0xC3, 0x7E, 0x6D, 0x33,
+                                       0xCF, 0xE2, 0x69, 0x9D,
+                                       0x74, 0xE6, 0xF6, 0x8A,
+                                       0x9E, 0x47, 0xA7, 0xCA};
+
+class SSLIdentityTest : public testing::Test {
+ public:
+  SSLIdentityTest() :
+      identity1_(NULL), identity2_(NULL) {
+  }
+
+  ~SSLIdentityTest() {
+  }
+
+  virtual void SetUp() {
+    identity1_.reset(talk_base::SSLIdentity::Generate("test1"));
+    identity2_.reset(talk_base::SSLIdentity::Generate("test2"));
+
+    ASSERT_TRUE(identity1_.get() != NULL);
+    ASSERT_TRUE(identity2_.get() != NULL);
+
+    test_cert_.reset(
+        talk_base::SSLCertificate::FromPEMString(kTestCertificate, 0));
+    ASSERT_TRUE(test_cert_.get() != NULL);
+  }
+
+  void TestDigest(const std::string &algorithm, size_t expected_len,
+                  const unsigned char *expected_digest = NULL) {
+    unsigned char digest1[64];
+    unsigned char digest1b[64];
+    unsigned char digest2[64];
+    size_t digest1_len;
+    size_t digest1b_len;
+    size_t digest2_len;
+    bool rv;
+
+    rv = identity1_->certificate().ComputeDigest(algorithm,
+                                                 digest1, sizeof(digest1),
+                                                 &digest1_len);
+    EXPECT_TRUE(rv);
+    EXPECT_EQ(expected_len, digest1_len);
+
+    rv = identity1_->certificate().ComputeDigest(algorithm,
+                                                 digest1b, sizeof(digest1b),
+                                                 &digest1b_len);
+    EXPECT_TRUE(rv);
+    EXPECT_EQ(expected_len, digest1b_len);
+    EXPECT_EQ(0, memcmp(digest1, digest1b, expected_len));
+
+
+    rv = identity2_->certificate().ComputeDigest(algorithm,
+                                                 digest2, sizeof(digest2),
+                                                 &digest2_len);
+    EXPECT_TRUE(rv);
+    EXPECT_EQ(expected_len, digest2_len);
+    EXPECT_NE(0, memcmp(digest1, digest2, expected_len));
+
+    // If we have an expected hash for the test cert, check it.
+    if (expected_digest != NULL) {
+      unsigned char digest3[64];
+      size_t digest3_len;
+
+      rv = test_cert_->ComputeDigest(algorithm, digest3, sizeof(digest3),
+                                    &digest3_len);
+      EXPECT_TRUE(rv);
+      EXPECT_EQ(expected_len, digest3_len);
+      EXPECT_EQ(0, memcmp(digest3, expected_digest, expected_len));
+    }
+  }
+
+ private:
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity1_;
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity2_;
+  talk_base::scoped_ptr<talk_base::SSLCertificate> test_cert_;
+};
+
+TEST_F(SSLIdentityTest, DigestSHA1) {
+  TestDigest(talk_base::DIGEST_SHA_1, 20, kTestCertSha1);
+}
+
+TEST_F(SSLIdentityTest, DigestSHA224) {
+  TestDigest(talk_base::DIGEST_SHA_224, 28);
+}
+
+TEST_F(SSLIdentityTest, DigestSHA256) {
+  TestDigest(talk_base::DIGEST_SHA_256, 32);
+}
+
+TEST_F(SSLIdentityTest, DigestSHA384) {
+  TestDigest(talk_base::DIGEST_SHA_384, 48);
+}
+
+TEST_F(SSLIdentityTest, DigestSHA512) {
+  TestDigest(talk_base::DIGEST_SHA_512, 64);
+}
diff --git a/talk/base/sslstreamadapter.cc b/talk/base/sslstreamadapter.cc
index 17121fd..aa0f468 100644
--- a/talk/base/sslstreamadapter.cc
+++ b/talk/base/sslstreamadapter.cc
@@ -65,6 +65,23 @@
 #endif  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
 }
 
+// Note: this matches the logic above with SCHANNEL dominating
+#if SSL_USE_SCHANNEL || !SSL_USE_OPENSSL
+bool SSLStreamAdapter::HaveDtls() { return false; }
+bool SSLStreamAdapter::HaveDtlsSrtp() { return false; }
+bool SSLStreamAdapter::HaveExporter() { return false; }
+#else
+bool SSLStreamAdapter::HaveDtls() {
+  return OpenSSLStreamAdapter::HaveDtls();
+}
+bool SSLStreamAdapter::HaveDtlsSrtp() {
+  return OpenSSLStreamAdapter::HaveDtlsSrtp();
+}
+bool SSLStreamAdapter::HaveExporter() {
+  return OpenSSLStreamAdapter::HaveExporter();
+}
+#endif  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+
 ///////////////////////////////////////////////////////////////////////////////
 
 }  // namespace talk_base
diff --git a/talk/base/sslstreamadapter.h b/talk/base/sslstreamadapter.h
index 62cef08..2afe1da 100644
--- a/talk/base/sslstreamadapter.h
+++ b/talk/base/sslstreamadapter.h
@@ -28,6 +28,9 @@
 #ifndef TALK_BASE_SSLSTREAMADAPTER_H__
 #define TALK_BASE_SSLSTREAMADAPTER_H__
 
+#include <string>
+#include <vector>
+
 #include "talk/base/stream.h"
 #include "talk/base/sslidentity.h"
 
@@ -47,6 +50,13 @@
 // for doing this are in SSLAdapter. They should possibly be moved out
 // to a neutral class.
 
+
+enum SSLRole { SSL_CLIENT, SSL_SERVER };
+enum SSLMode { SSL_MODE_TLS, SSL_MODE_DTLS };
+
+// Errors for Read -- in the high range so no conflict with OpenSSL.
+enum { SSE_MSG_TRUNC = 0xff0001 };
+
 class SSLStreamAdapter : public StreamAdapterInterface {
  public:
   // Instantiate an SSLStreamAdapter wrapping the given stream,
@@ -70,7 +80,12 @@
 
   // Call this to indicate that we are to play the server's role in
   // the peer-to-peer mode.
-  virtual void SetServerRole() = 0;
+  // The default argument is for backward compatibility
+  // TODO(ekr@rtfm.com): rename this SetRole to reflect its new function
+  virtual void SetServerRole(SSLRole role = SSL_SERVER) = 0;
+
+  // Do DTLS or TLS
+  virtual void SetMode(SSLMode mode) = 0;
 
   // The mode of operation is selected by calling either
   // StartSSLWithServer or StartSSLWithPeer.
@@ -95,7 +110,7 @@
   // StartSSLWithPeer starts negotiation in the special peer-to-peer
   // mode.
   // Generally, SetIdentity() and possibly SetServerRole() should have
-  // been called before this. 
+  // been called before this.
   // SetPeerCertificate() must also be called. It may be called after
   // StartSSLWithPeer() but must be called before the underlying
   // stream opens.
@@ -112,6 +127,53 @@
   // given SSLStream instance.
   virtual void SetPeerCertificate(SSLCertificate* cert) = 0;
 
+  // Specify the digest of the certificate that our peer is expected to use in
+  // peer-to-peer mode. Only this certificate will be accepted during
+  // SSL verification. The certificate is assumed to have been
+  // obtained through some other secure channel (such as the XMPP
+  // channel). Unlike SetPeerCertificate(), this must specify the
+  // terminal certificate, not just a CA.
+  // SSLStream makes a copy of the digest value.
+  virtual bool SetPeerCertificateDigest(const std::string& digest_alg,
+                                        const unsigned char* digest_val,
+                                        size_t digest_len) = 0;
+
+  // Key Exporter interface from RFC 5705
+  // Arguments are:
+  // label               -- the exporter label.
+  //                        part of the RFC defining each exporter
+  //                        usage (IN)
+  // context/context_len -- a context to bind to for this connection;
+  //                        optional, can be NULL, 0 (IN)
+  // use_context         -- whether to use the context value
+  //                        (needed to distinguish no context from
+  //                        zero-length ones).
+  // result              -- where to put the computed value
+  // result_len          -- the length of the computed value
+  virtual bool ExportKeyingMaterial(const std::string& label,
+                                    const uint8* context,
+                                    size_t context_len,
+                                    bool use_context,
+                                    uint8* result,
+                                    size_t result_len) {
+    return false;  // Default is unsupported
+  }
+
+
+  // DTLS-SRTP interface
+  virtual bool SetDtlsSrtpCiphers(const std::vector<std::string>& ciphers) {
+    return false;
+  }
+
+  virtual bool GetDtlsSrtpCipher(std::string* cipher) {
+    return false;
+  }
+
+  // Capabilities testing
+  static bool HaveDtls();
+  static bool HaveDtlsSrtp();
+  static bool HaveExporter();
+
   // If true, the server certificate need not match the configured
   // server_name, and in fact missing certificate authority and other
   // verification errors are ignored.
diff --git a/talk/base/sslstreamadapter_unittest.cc b/talk/base/sslstreamadapter_unittest.cc
new file mode 100644
index 0000000..befcdbc
--- /dev/null
+++ b/talk/base/sslstreamadapter_unittest.cc
@@ -0,0 +1,756 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ * Portions Copyright 2011, RTFM, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <set>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/stream.h"
+
+static const int kBlockSize = 4096;
+static const char kAES_CM_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80";
+static const char kAES_CM_HMAC_SHA1_32[] = "AES_CM_128_HMAC_SHA1_32";
+static const char kExporterLabel[] = "label";
+static const unsigned char kExporterContext[] = "context";
+static int kExporterContextLen = sizeof(kExporterContext);
+
+#define MAYBE_SKIP_TEST(feature)                    \
+  if (!(talk_base::SSLStreamAdapter::feature())) {  \
+    LOG(LS_INFO) << "Feature disabled... skipping"; \
+    return;                                         \
+  }
+
+class SSLStreamAdapterTestBase;
+
+class SSLDummyStream : public talk_base::StreamInterface,
+                       public sigslot::has_slots<> {
+ public:
+  explicit SSLDummyStream(SSLStreamAdapterTestBase *test,
+                          const std::string &side,
+                          talk_base::FifoBuffer *in,
+                          talk_base::FifoBuffer *out) :
+      test_(test),
+      side_(side),
+      in_(in),
+      out_(out),
+      first_packet_(true) {
+    in_->SignalEvent.connect(this, &SSLDummyStream::OnEventIn);
+    out_->SignalEvent.connect(this, &SSLDummyStream::OnEventOut);
+  }
+
+  virtual talk_base::StreamState GetState() const { return talk_base::SS_OPEN; }
+
+  virtual talk_base::StreamResult Read(void* buffer, size_t buffer_len,
+                                       size_t* read, int* error) {
+    talk_base::StreamResult r;
+
+    r = in_->Read(buffer, buffer_len, read, error);
+    if (r == talk_base::SR_BLOCK)
+      return talk_base::SR_BLOCK;
+    if (r == talk_base::SR_EOS)
+      return talk_base::SR_EOS;
+
+    if (r != talk_base::SR_SUCCESS) {
+      ADD_FAILURE();
+      return talk_base::SR_ERROR;
+    }
+
+    return talk_base::SR_SUCCESS;
+  }
+
+  // Catch readability events on in and pass them up.
+  virtual void OnEventIn(talk_base::StreamInterface *stream, int sig,
+                         int err) {
+    int mask = (talk_base::SE_READ | talk_base::SE_CLOSE);
+
+    if (sig & mask) {
+      LOG(LS_INFO) << "SSLDummyStream::OnEvent side=" << side_ <<  " sig="
+        << sig << " forwarding upward";
+      PostEvent(sig & mask, 0);
+    }
+  }
+
+  // Catch writeability events on out and pass them up.
+  virtual void OnEventOut(talk_base::StreamInterface *stream, int sig,
+                          int err) {
+    if (sig & talk_base::SE_WRITE) {
+      LOG(LS_INFO) << "SSLDummyStream::OnEvent side=" << side_ <<  " sig="
+        << sig << " forwarding upward";
+
+      PostEvent(sig & talk_base::SE_WRITE, 0);
+    }
+  }
+
+  // Write to the outgoing FifoBuffer
+  talk_base::StreamResult WriteData(const void* data, size_t data_len,
+                                    size_t* written, int* error) {
+    return out_->Write(data, data_len, written, error);
+  }
+
+  // Defined later
+  virtual talk_base::StreamResult Write(const void* data, size_t data_len,
+                                        size_t* written, int* error);
+
+  virtual void Close() {
+    LOG(LS_INFO) << "Closing outbound stream";
+    out_->Close();
+  }
+
+ private:
+  SSLStreamAdapterTestBase *test_;
+  const std::string side_;
+  talk_base::FifoBuffer *in_;
+  talk_base::FifoBuffer *out_;
+  bool first_packet_;
+};
+
+class SSLStreamAdapterTestBase : public testing::Test,
+                                 public sigslot::has_slots<> {
+ public:
+  explicit SSLStreamAdapterTestBase(bool dtls) :
+      client_buffer_(4096), server_buffer_(4096),
+      client_stream_(
+          new SSLDummyStream(this, "c2s", &client_buffer_, &server_buffer_)),
+      server_stream_(
+          new SSLDummyStream(this, "s2c", &server_buffer_, &client_buffer_)),
+      client_ssl_(talk_base::SSLStreamAdapter::Create(client_stream_)),
+      server_ssl_(talk_base::SSLStreamAdapter::Create(server_stream_)),
+      client_identity_(NULL), server_identity_(NULL),
+      delay_(0), mtu_(1460), loss_(0), lose_first_packet_(false), dtls_(dtls),
+      handshake_wait_(5000), identities_set_(false) {
+    // Set use of the test RNG to get predictable loss patterns.
+    talk_base::SetRandomTestMode(true);
+
+    LOG(LS_INFO) << "Setup";
+    talk_base::InitializeSSL();
+
+    // Set up the slots
+    client_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent);
+    server_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent);
+
+    client_identity_ = talk_base::SSLIdentity::Generate("client");
+    server_identity_ = talk_base::SSLIdentity::Generate("server");
+
+    client_ssl_->SetIdentity(client_identity_);
+    server_ssl_->SetIdentity(server_identity_);
+  }
+
+  ~SSLStreamAdapterTestBase() {
+    // Put it back for the next test.
+    talk_base::SetRandomTestMode(false);
+  }
+
+  virtual void OnEvent(talk_base::StreamInterface *stream, int sig, int err) {
+    LOG(LS_INFO) << "SSLStreamAdapterTestBase::OnEvent sig=" << sig;
+
+    if (sig & talk_base::SE_READ) {
+      ReadData(stream);
+    }
+
+    if ((stream == client_ssl_.get()) && (sig & talk_base::SE_WRITE)) {
+      WriteData();
+    }
+  }
+
+  void SetPeerIdentitiesByCertificate(bool correct) {
+    LOG(LS_INFO) << "Setting peer identities by certificate";
+
+    if (correct) {
+      client_ssl_->SetPeerCertificate(server_identity_->certificate().
+                                           GetReference());
+      server_ssl_->SetPeerCertificate(client_identity_->certificate().
+                                           GetReference());
+    } else {
+      // If incorrect, set up to expect our own certificate at the peer
+      client_ssl_->SetPeerCertificate(client_identity_->certificate().
+                                           GetReference());
+      server_ssl_->SetPeerCertificate(server_identity_->certificate().
+                                           GetReference());
+    }
+    identities_set_ = true;
+  }
+
+  void SetPeerIdentitiesByDigest(bool correct) {
+    unsigned char digest[20];
+    size_t digest_len;
+    bool rv;
+
+    LOG(LS_INFO) << "Setting peer identities by digest";
+
+    rv = server_identity_->certificate().ComputeDigest(talk_base::DIGEST_SHA_1,
+                                                      digest, 20,
+                                                      &digest_len);
+    ASSERT_TRUE(rv);
+    if (!correct) {
+      LOG(LS_INFO) << "Setting bogus digest for server cert";
+      digest[0]++;
+    }
+    rv = client_ssl_->SetPeerCertificateDigest(talk_base::DIGEST_SHA_1, digest,
+                                               digest_len);
+    ASSERT_TRUE(rv);
+
+
+    rv = client_identity_->certificate().ComputeDigest(talk_base::DIGEST_SHA_1,
+                                                      digest, 20, &digest_len);
+    ASSERT_TRUE(rv);
+    if (!correct) {
+      LOG(LS_INFO) << "Setting bogus digest for client cert";
+      digest[0]++;
+    }
+    rv = server_ssl_->SetPeerCertificateDigest(talk_base::DIGEST_SHA_1, digest,
+                                               digest_len);
+    ASSERT_TRUE(rv);
+
+    identities_set_ = true;
+  }
+
+  void TestHandshake(bool expect_success = true) {
+    server_ssl_->SetMode(dtls_ ? talk_base::SSL_MODE_DTLS :
+                         talk_base::SSL_MODE_TLS);
+    client_ssl_->SetMode(dtls_ ? talk_base::SSL_MODE_DTLS :
+                         talk_base::SSL_MODE_TLS);
+
+    if (!dtls_) {
+      // Make sure we simulate a reliable network for TLS.
+      // This is just a check to make sure that people don't write wrong
+      // tests.
+      ASSERT((mtu_ == 1460) && (loss_ == 0) && (lose_first_packet_ == 0));
+    }
+
+    if (!identities_set_)
+      SetPeerIdentitiesByDigest(true);
+
+    // Start the handshake
+    int rv;
+
+    server_ssl_->SetServerRole();
+    rv = server_ssl_->StartSSLWithPeer();
+    ASSERT_EQ(0, rv);
+
+    rv = client_ssl_->StartSSLWithPeer();
+    ASSERT_EQ(0, rv);
+
+    // Now run the handshake
+    if (expect_success) {
+      EXPECT_TRUE_WAIT((client_ssl_->GetState() == talk_base::SS_OPEN)
+                       && (server_ssl_->GetState() == talk_base::SS_OPEN),
+                       handshake_wait_);
+    } else {
+      EXPECT_TRUE_WAIT(client_ssl_->GetState() == talk_base::SS_CLOSED,
+                       handshake_wait_);
+    }
+  }
+
+  talk_base::StreamResult DataWritten(SSLDummyStream *from, const void *data,
+                                      size_t data_len, size_t *written,
+                                      int *error) {
+    // Randomly drop loss_ percent of packets
+    if (talk_base::CreateRandomId() % 100 < static_cast<uint32>(loss_)) {
+      LOG(LS_INFO) << "Randomly dropping packet, size=" << data_len;
+      *written = data_len;
+      return talk_base::SR_SUCCESS;
+    }
+    if (dtls_ && (data_len > mtu_)) {
+      LOG(LS_INFO) << "Dropping packet > mtu, size=" << data_len;
+      *written = data_len;
+      return talk_base::SR_SUCCESS;
+    }
+
+    return from->WriteData(data, data_len, written, error);
+  }
+
+  void SetDelay(int delay) {
+    delay_ = delay;
+  }
+  int GetDelay() { return delay_; }
+
+  void SetLoseFirstPacket(bool lose) {
+    lose_first_packet_ = lose;
+  }
+  bool GetLoseFirstPacket() { return lose_first_packet_; }
+
+  void SetLoss(int percent) {
+    loss_ = percent;
+  }
+
+  void SetMtu(size_t mtu) {
+    mtu_ = mtu;
+  }
+
+  void SetHandshakeWait(int wait) {
+    handshake_wait_ = wait;
+  }
+
+  void SetDtlsSrtpCiphers(const std::vector<std::string> &ciphers,
+    bool client) {
+    if (client)
+      client_ssl_->SetDtlsSrtpCiphers(ciphers);
+    else
+      server_ssl_->SetDtlsSrtpCiphers(ciphers);
+  }
+
+  bool GetDtlsSrtpCipher(bool client, std::string *retval) {
+    if (client)
+      return client_ssl_->GetDtlsSrtpCipher(retval);
+    else
+      return server_ssl_->GetDtlsSrtpCipher(retval);
+  }
+
+  bool ExportKeyingMaterial(const char *label,
+                            const unsigned char *context,
+                            size_t context_len,
+                            bool use_context,
+                            bool client,
+                            unsigned char *result,
+                            size_t result_len) {
+    if (client)
+      return client_ssl_->ExportKeyingMaterial(label,
+                                               context, context_len,
+                                               use_context,
+                                               result, result_len);
+    else
+      return server_ssl_->ExportKeyingMaterial(label,
+                                               context, context_len,
+                                               use_context,
+                                               result, result_len);
+  }
+
+  // To be implemented by subclasses.
+  virtual void WriteData() = 0;
+  virtual void ReadData(talk_base::StreamInterface *stream) = 0;
+  virtual void TestTransfer(int size) = 0;
+
+ protected:
+  talk_base::FifoBuffer client_buffer_;
+  talk_base::FifoBuffer server_buffer_;
+  SSLDummyStream *client_stream_;  // freed by client_ssl_ destructor
+  SSLDummyStream *server_stream_;  // freed by server_ssl_ destructor
+  talk_base::scoped_ptr<talk_base::SSLStreamAdapter> client_ssl_;
+  talk_base::scoped_ptr<talk_base::SSLStreamAdapter> server_ssl_;
+  talk_base::SSLIdentity *client_identity_;  // freed by client_ssl_ destructor
+  talk_base::SSLIdentity *server_identity_;  // freed by server_ssl_ destructor
+  int delay_;
+  size_t mtu_;
+  int loss_;
+  bool lose_first_packet_;
+  bool dtls_;
+  int handshake_wait_;
+  bool identities_set_;
+};
+
+
+class SSLStreamAdapterTestTLS : public SSLStreamAdapterTestBase {
+ public:
+  SSLStreamAdapterTestTLS() :
+      SSLStreamAdapterTestBase(false) {
+  };
+
+  // Test data transfer for TLS
+  virtual void TestTransfer(int size) {
+    LOG(LS_INFO) << "Starting transfer test with " << size << " bytes";
+    // Create some dummy data to send.
+    size_t received;
+
+    send_stream_.ReserveSize(size);
+    for (int i = 0; i < size; ++i) {
+      char ch = static_cast<char>(i);
+      send_stream_.Write(&ch, 1, NULL, NULL);
+    }
+    send_stream_.Rewind();
+
+    // Prepare the receive stream.
+    recv_stream_.ReserveSize(size);
+
+    // Start sending
+    WriteData();
+
+    // Wait for the client to close
+    EXPECT_TRUE_WAIT(server_ssl_->GetState() == talk_base::SS_CLOSED, 10000);
+
+    // Now check the data
+    recv_stream_.GetSize(&received);
+    EXPECT_EQ(static_cast<size_t>(size), received);
+    EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(),
+                        recv_stream_.GetBuffer(), size));
+  }
+
+  void WriteData() {
+    size_t position, tosend, size;
+    talk_base::StreamResult rv;
+    size_t sent;
+    char block[kBlockSize];
+
+    send_stream_.GetSize(&size);
+    if (!size)
+      return;
+
+    for (;;) {
+      send_stream_.GetPosition(&position);
+      if (send_stream_.Read(block, sizeof(block), &tosend, NULL) !=
+          talk_base::SR_EOS) {
+        rv = client_ssl_->Write(block, tosend, &sent, 0);
+
+        if (rv == talk_base::SR_SUCCESS) {
+          send_stream_.SetPosition(position + sent);
+          LOG(LS_VERBOSE) << "Sent: " << position + sent;
+        } else if (rv == talk_base::SR_BLOCK) {
+          LOG(LS_VERBOSE) << "Blocked...";
+          send_stream_.SetPosition(position);
+          break;
+        } else {
+          ADD_FAILURE();
+          break;
+        }
+      } else {
+        // Now close
+        LOG(LS_INFO) << "Wrote " << position << " bytes. Closing";
+        client_ssl_->Close();
+        break;
+      }
+    }
+  };
+
+  virtual void ReadData(talk_base::StreamInterface *stream) {
+    char buffer[1600];
+    size_t bread;
+    int err2;
+    talk_base::StreamResult r;
+
+    for (;;) {
+      r = stream->Read(buffer, sizeof(buffer), &bread, &err2);
+
+      if (r == talk_base::SR_ERROR) {
+        // Unfortunately, errors are the way that the stream adapter
+        // signals close right now
+        stream->Close();
+        return;
+      }
+
+      if (r == talk_base::SR_BLOCK)
+        break;
+
+      ASSERT_EQ(talk_base::SR_SUCCESS, r);
+      LOG(LS_INFO) << "Read " << bread;
+
+      recv_stream_.Write(buffer, bread, NULL, NULL);
+    }
+  }
+
+ private:
+  talk_base::MemoryStream send_stream_;
+  talk_base::MemoryStream recv_stream_;
+};
+
+
+class SSLStreamAdapterTestDTLS : public SSLStreamAdapterTestBase {
+ public:
+  SSLStreamAdapterTestDTLS() :
+      SSLStreamAdapterTestBase(true),
+      packet_size_(1000), count_(0), sent_(0) {
+  }
+
+  virtual void WriteData() {
+    unsigned char *packet = new unsigned char[1600];
+
+    do {
+      memset(packet, sent_ & 0xff, packet_size_);
+      *(reinterpret_cast<uint32_t *>(packet)) = sent_;
+
+      size_t sent;
+      int rv = client_ssl_->Write(packet, packet_size_, &sent, 0);
+      if (rv == talk_base::SR_SUCCESS) {
+        LOG(LS_VERBOSE) << "Sent: " << sent_;
+        sent_++;
+      } else if (rv == talk_base::SR_BLOCK) {
+        LOG(LS_VERBOSE) << "Blocked...";
+        break;
+      } else {
+        ADD_FAILURE();
+        break;
+      }
+    } while (sent_ < count_);
+
+    delete [] packet;
+  }
+
+  virtual void ReadData(talk_base::StreamInterface *stream) {
+    unsigned char *buffer = new unsigned char[2000];
+    size_t bread;
+    int err2;
+    talk_base::StreamResult r;
+
+    for (;;) {
+      r = stream->Read(buffer, 2000,
+                       &bread, &err2);
+
+      if (r == talk_base::SR_ERROR) {
+        // Unfortunately, errors are the way that the stream adapter
+        // signals close right now
+        stream->Close();
+        return;
+      }
+
+      if (r == talk_base::SR_BLOCK)
+        break;
+
+      ASSERT_EQ(talk_base::SR_SUCCESS, r);
+      LOG(LS_INFO) << "Read " << bread;
+
+      // Now parse the datagram
+      ASSERT_EQ(packet_size_, bread);
+      uint32_t packet_num = *(reinterpret_cast<uint32_t *>(buffer));
+
+      for (size_t i = 4; i < packet_size_; i++) {
+        ASSERT_EQ((packet_num & 0xff), buffer[i]);
+      }
+      received_.insert(packet_num);
+    }
+  }
+
+  virtual void TestTransfer(int count) {
+    count_ = count;
+
+    WriteData();
+
+    EXPECT_TRUE_WAIT(sent_ == count_, 10000);
+    LOG(LS_INFO) << "sent_ == " << sent_;
+
+    if (loss_ == 0) {
+      EXPECT_TRUE_WAIT(static_cast<size_t>(sent_) == received_.size(), 1000);
+    } else {
+      LOG(LS_INFO) << "Sent " << sent_ << " packets; received " <<
+          received_.size();
+    }
+  };
+
+ private:
+  size_t packet_size_;
+  int count_;
+  int sent_;
+  std::set<int> received_;
+};
+
+
+talk_base::StreamResult SSLDummyStream::Write(const void* data, size_t data_len,
+                                              size_t* written, int* error) {
+  *written = data_len;
+
+  LOG(LS_INFO) << "Writing to loopback " << data_len;
+
+  if (first_packet_) {
+    first_packet_ = false;
+    if (test_->GetLoseFirstPacket()) {
+      LOG(LS_INFO) << "Losing initial packet of length " << data_len;
+      return talk_base::SR_SUCCESS;
+    }
+  }
+
+  return test_->DataWritten(this, data, data_len, written, error);
+
+  return talk_base::SR_SUCCESS;
+};
+
+
+// Basic tests: TLS
+// Test that we can make a handshake work
+TEST_F(SSLStreamAdapterTestTLS, TestTLSConnect) {
+  TestHandshake();
+};
+
+// Test transfer -- trivial
+TEST_F(SSLStreamAdapterTestTLS, TestTLSTransfer) {
+  TestHandshake();
+  TestTransfer(100000);
+};
+
+// Test a handshake with a bogus peer digest
+TEST_F(SSLStreamAdapterTestTLS, TestTLSBogusDigest) {
+  SetPeerIdentitiesByDigest(false);
+  TestHandshake(false);
+};
+
+// Test a handshake with a peer certificate
+TEST_F(SSLStreamAdapterTestTLS, TestTLSPeerCertificate) {
+  SetPeerIdentitiesByCertificate(true);
+  TestHandshake();
+};
+
+// Test a handshake with a bogus peer certificate
+TEST_F(SSLStreamAdapterTestTLS, TestTLSBogusPeerCertificate) {
+  SetPeerIdentitiesByCertificate(false);
+  TestHandshake(false);
+};
+// Test moving a bunch of data
+
+// Basic tests: DTLS
+// Test that we can make a handshake work
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnect) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  TestHandshake();
+};
+
+// Test that we can make a handshake work if the first packet in
+// each direction is lost. This gives us predictable loss
+// rather than having to tune random
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithLostFirstPacket) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  SetLoseFirstPacket(true);
+  TestHandshake();
+};
+
+// Test a handshake with loss and delay
+TEST_F(SSLStreamAdapterTestDTLS,
+       TestDTLSConnectWithLostFirstPacketDelay2s) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  SetLoseFirstPacket(true);
+  SetDelay(2000);
+  SetHandshakeWait(20000);
+  TestHandshake();
+};
+
+// Test a handshake with small MTU
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithSmallMtu) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  SetMtu(700);
+  TestHandshake();
+};
+
+// Test transfer -- trivial
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransfer) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  TestHandshake();
+  TestTransfer(100);
+};
+
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransferWithLoss) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  TestHandshake();
+  SetLoss(10);
+  TestTransfer(100);
+};
+
+// Test DTLS-SRTP with all high ciphers
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpHigh) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  std::vector<std::string> high;
+  high.push_back(kAES_CM_HMAC_SHA1_80);
+  SetDtlsSrtpCiphers(high, true);
+  SetDtlsSrtpCiphers(high, false);
+  TestHandshake();
+
+  std::string client_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher));
+  std::string server_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher));
+
+  ASSERT_EQ(client_cipher, server_cipher);
+  ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_80);
+};
+
+// Test DTLS-SRTP with all low ciphers
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpLow) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  std::vector<std::string> low;
+  low.push_back(kAES_CM_HMAC_SHA1_32);
+  SetDtlsSrtpCiphers(low, true);
+  SetDtlsSrtpCiphers(low, false);
+  TestHandshake();
+
+  std::string client_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher));
+  std::string server_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher));
+
+  ASSERT_EQ(client_cipher, server_cipher);
+  ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_32);
+};
+
+
+// Test DTLS-SRTP with a mismatch -- should not converge
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpHighLow) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  std::vector<std::string> high;
+  high.push_back(kAES_CM_HMAC_SHA1_80);
+  std::vector<std::string> low;
+  low.push_back(kAES_CM_HMAC_SHA1_32);
+  SetDtlsSrtpCiphers(high, true);
+  SetDtlsSrtpCiphers(low, false);
+  TestHandshake();
+
+  std::string client_cipher;
+  ASSERT_FALSE(GetDtlsSrtpCipher(true, &client_cipher));
+  std::string server_cipher;
+  ASSERT_FALSE(GetDtlsSrtpCipher(false, &server_cipher));
+};
+
+// Test DTLS-SRTP with each side being mixed -- should select high
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpMixed) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  std::vector<std::string> mixed;
+  mixed.push_back(kAES_CM_HMAC_SHA1_80);
+  mixed.push_back(kAES_CM_HMAC_SHA1_32);
+  SetDtlsSrtpCiphers(mixed, true);
+  SetDtlsSrtpCiphers(mixed, false);
+  TestHandshake();
+
+  std::string client_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher));
+  std::string server_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher));
+
+  ASSERT_EQ(client_cipher, server_cipher);
+  ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_80);
+};
+
+// Test an exporter
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSExporter) {
+  MAYBE_SKIP_TEST(HaveExporter);
+  TestHandshake();
+  unsigned char client_out[20];
+  unsigned char server_out[20];
+
+  bool result;
+  result = ExportKeyingMaterial(kExporterLabel,
+                                kExporterContext, kExporterContextLen,
+                                true, true,
+                                client_out, sizeof(client_out));
+  ASSERT_TRUE(result);
+
+  result = ExportKeyingMaterial(kExporterLabel,
+                                kExporterContext, kExporterContextLen,
+                                true, false,
+                                server_out, sizeof(server_out));
+  ASSERT_TRUE(result);
+
+  ASSERT_TRUE(!memcmp(client_out, server_out, sizeof(client_out)));
+}
diff --git a/talk/base/stream.cc b/talk/base/stream.cc
index 9fcb1a8..24a97dc 100644
--- a/talk/base/stream.cc
+++ b/talk/base/stream.cc
@@ -50,11 +50,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 // StreamInterface
 ///////////////////////////////////////////////////////////////////////////////
-
-enum {
-  MSG_POST_EVENT = 0xF1F1
-};
-
 StreamInterface::~StreamInterface() {
 }
 
@@ -174,9 +169,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 StreamTap::StreamTap(StreamInterface* stream, StreamInterface* tap)
-: StreamAdapterInterface(stream), tap_(NULL), tap_result_(SR_SUCCESS),
-  tap_error_(0)
-{
+    : StreamAdapterInterface(stream), tap_(NULL), tap_result_(SR_SUCCESS),
+        tap_error_(0) {
   AttachTap(tap);
 }
 
@@ -228,24 +222,21 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 StreamSegment::StreamSegment(StreamInterface* stream)
-: StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
-  length_(SIZE_UNKNOWN)
-{
+    : StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
+    length_(SIZE_UNKNOWN) {
   // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN.
   stream->GetPosition(&start_);
 }
 
 StreamSegment::StreamSegment(StreamInterface* stream, size_t length)
-: StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
-  length_(length)
-{
+    : StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
+    length_(length) {
   // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN.
   stream->GetPosition(&start_);
 }
 
 StreamResult StreamSegment::Read(void* buffer, size_t buffer_len,
-                                 size_t* read, int* error)
-{
+                                 size_t* read, int* error) {
   if (SIZE_UNKNOWN != length_) {
     if (pos_ >= length_)
       return SR_EOS;
@@ -744,6 +735,12 @@
   // all events are done on the owner_ thread
 }
 
+FifoBuffer::FifoBuffer(size_t size, Thread *owner)
+    : state_(SS_OPEN), buffer_(new char[size]), buffer_length_(size),
+      data_length_(0), read_position_(0), owner_(owner) {
+  // all events are done on the owner_ thread
+}
+
 FifoBuffer::~FifoBuffer() {
 }
 
@@ -872,7 +869,7 @@
 
   const size_t write_position = (read_position_ + data_length_)
       % buffer_length_;
-  *size = (write_position >= read_position_) ?
+  *size = (write_position > read_position_ || data_length_ == 0) ?
       buffer_length_ - write_position : read_position_ - write_position;
   return &buffer_[write_position];
 }
@@ -950,8 +947,7 @@
 
 LoggingAdapter::LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
                                const std::string& label, bool hex_mode)
-: StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode)
-{
+    : StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode) {
   set_label(label);
 }
 
@@ -974,7 +970,8 @@
 
 StreamResult LoggingAdapter::Write(const void* data, size_t data_len,
                                    size_t* written, int* error) {
-  size_t local_written; if (!written) written = &local_written;
+  size_t local_written;
+  if (!written) written = &local_written;
   StreamResult result = StreamAdapterInterface::Write(data, data_len, written,
                                                       error);
   if (result == SR_SUCCESS) {
@@ -1007,13 +1004,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 StringStream::StringStream(std::string& str)
-: str_(str), read_pos_(0), read_only_(false)
-{
+    : str_(str), read_pos_(0), read_only_(false) {
 }
 
 StringStream::StringStream(const std::string& str)
-: str_(const_cast<std::string&>(str)), read_pos_(0), read_only_(true)
-{
+    : str_(const_cast<std::string&>(str)), read_pos_(0), read_only_(true) {
 }
 
 StreamState StringStream::GetState() const {
@@ -1169,4 +1164,4 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-} // namespace talk_base
+}  // namespace talk_base
diff --git a/talk/base/stream.h b/talk/base/stream.h
index 0798741..808156c 100644
--- a/talk/base/stream.h
+++ b/talk/base/stream.h
@@ -67,6 +67,10 @@
 
 class StreamInterface : public MessageHandler {
  public:
+  enum {
+    MSG_POST_EVENT = 0xF1F1, MSG_MAX = MSG_POST_EVENT
+  };
+
   virtual ~StreamInterface();
 
   virtual StreamState GetState() const = 0;
@@ -125,7 +129,7 @@
   // latter operation but not the former.
   //
 
-  // The following four methods are used to avoid coping data multiple times.
+  // The following four methods are used to avoid copying data multiple times.
 
   // GetReadData returns a pointer to a buffer which is owned by the stream.
   // The buffer contains data_len bytes.  NULL is returned if no data is
@@ -556,6 +560,8 @@
  public:
   // Creates a FIFO buffer with the specified capacity.
   explicit FifoBuffer(size_t length);
+  // Creates a FIFO buffer with the specified capacity and owner
+  FifoBuffer(size_t length, Thread* owner);
   virtual ~FifoBuffer();
   // Gets the amount of data currently readable from the buffer.
   bool GetBuffered(size_t* data_len) const;
diff --git a/talk/base/stream_unittest.cc b/talk/base/stream_unittest.cc
index d0c5718..b96279b 100644
--- a/talk/base/stream_unittest.cc
+++ b/talk/base/stream_unittest.cc
@@ -35,14 +35,14 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 class TestStream : public StreamInterface {
-public:
+ 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) {
+    for (size_t i = 0; i < buffer_len; ++i) {
       uc_buffer[i] = pos_++;
     }
     if (read)
@@ -71,14 +71,14 @@
     return false;
   }
 
-private:
+ 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) {
+  for (size_t i = 0; i < len; ++i) {
     if (buffer[i] != value++) {
       passed = false;
       break;
@@ -393,6 +393,15 @@
   EXPECT_EQ(SR_EOS, stream->Read(out, kSize / 2, &bytes, NULL));
 }
 
+TEST(FifoBufferTest, FullBufferCheck) {
+  FifoBuffer buff(10);
+  buff.ConsumeWriteBuffer(10);
+
+  size_t free;
+  EXPECT_TRUE(buff.GetWriteBuffer(&free) != NULL);
+  EXPECT_EQ(0U, free);
+}
+
 TEST(FifoBufferTest, WriteOffsetAndReadOffset) {
   const size_t kSize = 16;
   const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
@@ -442,4 +451,4 @@
   EXPECT_EQ(SR_BLOCK, buf.ReadOffset(out, 10, 16, NULL));
 }
 
-} // namespace talk_base
+}  // namespace talk_base
diff --git a/talk/base/systeminfo.cc b/talk/base/systeminfo.cc
new file mode 100644
index 0000000..7cbc418
--- /dev/null
+++ b/talk/base/systeminfo.cc
@@ -0,0 +1,461 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef WIN32
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/win32.h"  // first because it brings in win32 stuff
+#ifndef EXCLUDE_D3D9
+#include <d3d9.h>
+#endif
+
+#elif defined(OSX)
+#include <ApplicationServices/ApplicationServices.h>
+#include <CoreServices/CoreServices.h>
+#include <sys/sysctl.h>
+#include "talk/base/macconversion.h"
+#elif defined(IOS)
+#include <sys/sysctl.h>
+#elif defined(LINUX) || defined(ANDROID)
+#include <unistd.h>
+#include "talk/base/linux.h"
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/cpuid.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/systeminfo.h"
+
+namespace talk_base {
+
+// See Also: http://msdn.microsoft.com/en-us/library/ms683194(v=vs.85).aspx
+#ifdef WIN32
+typedef BOOL (WINAPI *LPFN_GLPI)(
+    PSYSTEM_LOGICAL_PROCESSOR_INFORMATION,
+    PDWORD);
+
+static int NumCores() {
+  // GetLogicalProcessorInformation() is available on Windows XP SP3 and beyond.
+  LPFN_GLPI glpi = reinterpret_cast<LPFN_GLPI>(GetProcAddress(
+      GetModuleHandle(L"kernel32"),
+      "GetLogicalProcessorInformation"));
+  if (NULL == glpi) {
+    return -1;
+  }
+  // Determine buffer size, allocate and get processor information.
+  // Size can change between calls (unlikely), so a loop is done.
+  DWORD return_length = 0;
+  scoped_array<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> infos;
+  while (!glpi(infos.get(), &return_length)) {
+    if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+      infos.reset(new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[
+          return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)]);
+    } else {
+      return -1;
+    }
+  }
+  int processor_core_count = 0;
+  for (size_t i = 0;
+      i < return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) {
+    if (infos[i].Relationship == RelationProcessorCore) {
+      ++processor_core_count;
+    }
+  }
+  return processor_core_count;
+}
+#endif
+
+// Note(fbarchard):
+// Family and model are extended family and extended model.  8 bits each.
+SystemInfo::SystemInfo()
+    : physical_cpus_(1), logical_cpus_(1),
+      cpu_family_(0), cpu_model_(0), cpu_stepping_(0),
+      cpu_speed_(0), memory_(0) {
+  // Initialize the basic information.
+
+#if defined(__arm__)
+  cpu_arch_ = ARCH_ARM;
+#elif defined(CPU_X86)
+  cpu_arch_ = ARCH_X86;
+#else
+#error "Unknown architecture."
+#endif
+
+#ifdef WIN32
+  SYSTEM_INFO si;
+  GetSystemInfo(&si);
+  logical_cpus_ = si.dwNumberOfProcessors;
+  physical_cpus_ = NumCores();
+  if (physical_cpus_ <= 0) {
+    physical_cpus_ = logical_cpus_;
+  }
+  cpu_family_ = si.wProcessorLevel;
+  cpu_model_ = si.wProcessorRevision >> 8;
+  cpu_stepping_ = si.wProcessorRevision & 0xFF;
+#elif defined(OSX) || defined(IOS)
+  uint32_t sysctl_value;
+  size_t length = sizeof(sysctl_value);
+  if (!sysctlbyname("hw.physicalcpu_max", &sysctl_value, &length, NULL, 0)) {
+    physical_cpus_ = static_cast<int>(sysctl_value);
+  }
+  length = sizeof(sysctl_value);
+  if (!sysctlbyname("hw.logicalcpu_max", &sysctl_value, &length, NULL, 0)) {
+    logical_cpus_ = static_cast<int>(sysctl_value);
+  }
+  length = sizeof(sysctl_value);
+  if (!sysctlbyname("machdep.cpu.family", &sysctl_value, &length, NULL, 0)) {
+    cpu_family_ = static_cast<int>(sysctl_value);
+  }
+  length = sizeof(sysctl_value);
+  if (!sysctlbyname("machdep.cpu.model", &sysctl_value, &length, NULL, 0)) {
+    cpu_model_ = static_cast<int>(sysctl_value);
+  }
+  length = sizeof(sysctl_value);
+  if (!sysctlbyname("machdep.cpu.stepping", &sysctl_value, &length, NULL, 0)) {
+    cpu_stepping_ = static_cast<int>(sysctl_value);
+  }
+#else // LINUX || ANDROID
+  ProcCpuInfo proc_info;
+  if (proc_info.LoadFromSystem()) {
+    proc_info.GetNumCpus(&logical_cpus_);
+    proc_info.GetNumPhysicalCpus(&physical_cpus_);
+    proc_info.GetCpuFamily(&cpu_family_);
+#if !defined(__arm__)
+    // These values aren't found on ARM systems.
+    proc_info.GetSectionIntValue(0, "model", &cpu_model_);
+    proc_info.GetSectionIntValue(0, "stepping", &cpu_stepping_);
+    proc_info.GetSectionIntValue(0, "cpu MHz", &cpu_speed_);
+#endif
+  }
+
+  // ProcCpuInfo reads cpu speed from "cpu MHz" under /proc/cpuinfo.
+  // But that number is a moving target which can change on-the-fly according to
+  // many factors including system workload.
+  // See /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors.
+  // The one in /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq is more
+  // accurate. We use it as our cpu speed when it is available.
+  int max_freq = talk_base::ReadCpuMaxFreq();
+  if (max_freq > 0) {
+    cpu_speed_ = max_freq;
+  }
+#endif
+}
+
+// Return the number of cpu threads available to the system.
+int SystemInfo::GetMaxCpus() {
+  return logical_cpus_;
+}
+
+// Return the number of cpu cores available to the system.
+int SystemInfo::GetMaxPhysicalCpus() {
+  return physical_cpus_;
+}
+
+// Return the number of cpus available to the process.  Since affinity can be
+// changed on the fly, do not cache this value.
+// Can be affected by heat.
+int SystemInfo::GetCurCpus() {
+  int cur_cpus;
+#ifdef WIN32
+  DWORD process_mask, system_mask;
+  ::GetProcessAffinityMask(::GetCurrentProcess(), &process_mask, &system_mask);
+  for (cur_cpus = 0; process_mask; ++cur_cpus) {
+    // Sparse-ones algorithm. There are slightly faster methods out there but
+    // they are unintuitive and won't make a difference on a single dword.
+    process_mask &= (process_mask - 1);
+  }
+#elif defined(OSX)
+  // Find number of _available_ cores
+  cur_cpus = MPProcessorsScheduled();
+#elif defined(IOS)
+  uint32_t sysctl_value;
+  size_t length = sizeof(sysctl_value);
+  int error = sysctlbyname("hw.ncpu", &sysctl_value, &length, NULL, 0);
+  cur_cpus = !error ? static_cast<int>(sysctl_value) : 1;
+#else
+  // Linux, Solaris, ANDROID
+  cur_cpus = static_cast<int>(sysconf(_SC_NPROCESSORS_ONLN));
+#endif
+  return cur_cpus;
+}
+
+// Return the type of this CPU.
+SystemInfo::Architecture SystemInfo::GetCpuArchitecture() {
+  return cpu_arch_;
+}
+
+// Returns the vendor string from the cpu, e.g. "GenuineIntel", "AuthenticAMD".
+// See "Intel Processor Identification and the CPUID Instruction"
+// (Intel document number: 241618)
+std::string SystemInfo::GetCpuVendor() {
+  if (cpu_vendor_.empty()) {
+    cpu_vendor_ = talk_base::CpuInfo::GetCpuVendor();
+  }
+  return cpu_vendor_;
+}
+
+// Return the "family" of this CPU.
+int SystemInfo::GetCpuFamily() {
+  return cpu_family_;
+}
+
+// Return the "model" of this CPU.
+int SystemInfo::GetCpuModel() {
+  return cpu_model_;
+}
+
+// Return the "stepping" of this CPU.
+int SystemInfo::GetCpuStepping() {
+  return cpu_stepping_;
+}
+
+// Return the clockrate of the primary processor in Mhz.  This value can be
+// cached.  Returns -1 on error.
+int SystemInfo::GetMaxCpuSpeed() {
+  if (cpu_speed_) {
+    return cpu_speed_;
+  }
+
+#ifdef WIN32
+  HKEY key;
+  static const WCHAR keyName[] =
+      L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
+
+  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName , 0, KEY_QUERY_VALUE, &key)
+      == ERROR_SUCCESS) {
+    DWORD data, len;
+    len = sizeof(data);
+
+    if (RegQueryValueEx(key, L"~Mhz", 0, 0, reinterpret_cast<LPBYTE>(&data),
+                        &len) == ERROR_SUCCESS) {
+      cpu_speed_ = data;
+    } else {
+      LOG(LS_WARNING) << "Failed to query registry value HKLM\\" << keyName
+                      << "\\~Mhz";
+      cpu_speed_ = -1;
+    }
+
+    RegCloseKey(key);
+  } else {
+    LOG(LS_WARNING) << "Failed to open registry key HKLM\\" << keyName;
+    cpu_speed_ = -1;
+  }
+#elif defined(IOS) || defined(OSX)
+  uint64_t sysctl_value;
+  size_t length = sizeof(sysctl_value);
+  int error = sysctlbyname("hw.cpufrequency_max", &sysctl_value, &length, NULL, 0);
+  cpu_speed_ = !error ? static_cast<int>(sysctl_value/1000000) : -1;
+#else
+  // TODO: Implement using proc/cpuinfo
+  cpu_speed_ = 0;
+#endif
+  return cpu_speed_;
+}
+
+// Dynamically check the current clockrate, which could be reduced because of
+// powersaving profiles.  Eventually for windows we want to query WMI for
+// root\WMI::ProcessorPerformance.InstanceName="Processor_Number_0".frequency
+int SystemInfo::GetCurCpuSpeed() {
+#ifdef WIN32
+  // TODO: Add WMI check, requires COM initialization
+  // NOTE(fbarchard): Testable on Sandy Bridge.
+  return GetMaxCpuSpeed();
+#elif defined(IOS) || defined(OSX)
+  uint64_t sysctl_value;
+  size_t length = sizeof(sysctl_value);
+  int error = sysctlbyname("hw.cpufrequency", &sysctl_value, &length, NULL, 0);
+  return !error ? static_cast<int>(sysctl_value/1000000) : GetMaxCpuSpeed();
+#else // LINUX || ANDROID
+  // TODO: Use proc/cpuinfo for Cur speed on Linux.
+  return GetMaxCpuSpeed();
+#endif
+}
+
+// Returns the amount of installed physical memory in Bytes.  Cacheable.
+// Returns -1 on error.
+int64 SystemInfo::GetMemorySize() {
+  if (memory_) {
+    return memory_;
+  }
+
+#ifdef WIN32
+  MEMORYSTATUSEX status = {0};
+  status.dwLength = sizeof(status);
+
+  if (GlobalMemoryStatusEx(&status)) {
+    memory_ = status.ullTotalPhys;
+  } else {
+    LOG_GLE(LS_WARNING) << "GlobalMemoryStatusEx failed.";
+    memory_ = -1;
+  }
+
+#elif defined(OSX) || defined(IOS)
+  size_t len = sizeof(memory_);
+  int error = sysctlbyname("hw.memsize", &memory_, &len, NULL, 0);
+  if (error || memory_ == 0) {
+    memory_ = -1;
+  }
+#else
+  memory_ = static_cast<int64>(sysconf(_SC_PHYS_PAGES)) *
+      static_cast<int64>(sysconf(_SC_PAGESIZE));
+  if (memory_ < 0) {
+    LOG(LS_WARNING) << "sysconf(_SC_PHYS_PAGES) failed."
+                    << "sysconf(_SC_PHYS_PAGES) " << sysconf(_SC_PHYS_PAGES)
+                    << "sysconf(_SC_PAGESIZE) " << sysconf(_SC_PAGESIZE);
+    memory_ = -1;
+  }
+#endif
+
+  return memory_;
+}
+
+
+// Return the name of the machine model we are currently running on.
+// This is a human readable string that consists of the name and version
+// number of the hardware, i.e 'MacBookAir1,1'. Returns an empty string if
+// model can not be determined. The string is cached for subsequent calls.
+std::string SystemInfo::GetMachineModel() {
+  if (!machine_model_.empty()) {
+    return machine_model_;
+  }
+
+#if defined(OSX) || defined(IOS)
+  char buffer[128];
+  size_t length = sizeof(buffer);
+  int error = sysctlbyname("hw.model", buffer, &length, NULL, 0);
+  if (!error) {
+    machine_model_.assign(buffer, length - 1);
+  } else {
+    machine_model_.clear();
+  }
+#else
+  machine_model_ = "Not available";
+#endif
+
+  return machine_model_;
+}
+
+#ifdef OSX
+// Helper functions to query IOKit for video hardware properties.
+static CFTypeRef SearchForProperty(io_service_t port, CFStringRef name) {
+  return IORegistryEntrySearchCFProperty(port, kIOServicePlane,
+      name, kCFAllocatorDefault,
+      kIORegistryIterateRecursively | kIORegistryIterateParents);
+}
+
+static void GetProperty(io_service_t port, CFStringRef name, int* value) {
+  if (!value) return;
+  CFTypeRef ref = SearchForProperty(port, name);
+  if (ref) {
+    CFTypeID refType = CFGetTypeID(ref);
+    if (CFNumberGetTypeID() == refType) {
+      CFNumberRef number = reinterpret_cast<CFNumberRef>(ref);
+      p_convertCFNumberToInt(number, value);
+    } else if (CFDataGetTypeID() == refType) {
+      CFDataRef data = reinterpret_cast<CFDataRef>(ref);
+      if (CFDataGetLength(data) == sizeof(UInt32)) {
+        *value = *reinterpret_cast<const UInt32*>(CFDataGetBytePtr(data));
+      }
+    }
+    CFRelease(ref);
+  }
+}
+
+static void GetProperty(io_service_t port, CFStringRef name,
+                        std::string* value) {
+  if (!value) return;
+  CFTypeRef ref = SearchForProperty(port, name);
+  if (ref) {
+    CFTypeID refType = CFGetTypeID(ref);
+    if (CFStringGetTypeID() == refType) {
+      CFStringRef stringRef = reinterpret_cast<CFStringRef>(ref);
+      p_convertHostCFStringRefToCPPString(stringRef, *value);
+    } else if (CFDataGetTypeID() == refType) {
+      CFDataRef dataRef = reinterpret_cast<CFDataRef>(ref);
+      *value = std::string(reinterpret_cast<const char*>(
+          CFDataGetBytePtr(dataRef)), CFDataGetLength(dataRef));
+    }
+    CFRelease(ref);
+  }
+}
+#endif
+
+// Fills a struct with information on the graphics adapater and returns true
+// iff successful.
+bool SystemInfo::GetGpuInfo(GpuInfo *info) {
+  if (!info) return false;
+#if defined(WIN32) && !defined(EXCLUDE_D3D9)
+  D3DADAPTER_IDENTIFIER9 identifier;
+  HRESULT hr = E_FAIL;
+  HINSTANCE d3d_lib = LoadLibrary(L"d3d9.dll");
+
+  if (d3d_lib) {
+    typedef IDirect3D9* (WINAPI *D3DCreate9Proc)(UINT);
+    D3DCreate9Proc d3d_create_proc = reinterpret_cast<D3DCreate9Proc>(
+        GetProcAddress(d3d_lib, "Direct3DCreate9"));
+    if (d3d_create_proc) {
+      IDirect3D9* d3d = d3d_create_proc(D3D_SDK_VERSION);
+      if (d3d) {
+        hr = d3d->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &identifier);
+        d3d->Release();
+      }
+    }
+    FreeLibrary(d3d_lib);
+  }
+
+  if (hr != D3D_OK) {
+    LOG(LS_ERROR) << "Failed to access Direct3D9 information.";
+    return false;
+  }
+
+  info->device_name = identifier.DeviceName;
+  info->description = identifier.Description;
+  info->vendor_id = identifier.VendorId;
+  info->device_id = identifier.DeviceId;
+  info->driver = identifier.Driver;
+  // driver_version format: product.version.subversion.build
+  std::stringstream ss;
+  ss << HIWORD(identifier.DriverVersion.HighPart) << "."
+     << LOWORD(identifier.DriverVersion.HighPart) << "."
+     << HIWORD(identifier.DriverVersion.LowPart) << "."
+     << LOWORD(identifier.DriverVersion.LowPart);
+  info->driver_version = ss.str();
+  return true;
+#elif defined(OSX)
+  // We'll query the IOKit for the gpu of the main display.
+  io_service_t display_service_port = CGDisplayIOServicePort(
+      kCGDirectMainDisplay);
+  GetProperty(display_service_port, CFSTR("vendor-id"), &info->vendor_id);
+  GetProperty(display_service_port, CFSTR("device-id"), &info->device_id);
+  GetProperty(display_service_port, CFSTR("model"), &info->description);
+  return true;
+#else // LINUX || ANDROID
+  // TODO: Implement this on Linux
+  return false;
+#endif
+}
+} // namespace talk_base
diff --git a/talk/base/systeminfo.h b/talk/base/systeminfo.h
new file mode 100644
index 0000000..cf8e537
--- /dev/null
+++ b/talk/base/systeminfo.h
@@ -0,0 +1,94 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SYSTEMINFO_H__
+#define TALK_BASE_SYSTEMINFO_H__
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+class SystemInfo {
+ public:
+  enum Architecture {
+    ARCH_X86 = 0,
+    ARCH_X64 = 1,
+    ARCH_ARM = 2
+  };
+
+  SystemInfo();
+
+  // The number of CPU Cores in the system.
+  int GetMaxPhysicalCpus();
+  // The number of CPU Threads in the system.
+  int GetMaxCpus();
+  // The number of CPU Threads currently available to this process.
+  int GetCurCpus();
+  // Identity of the CPUs.
+  Architecture GetCpuArchitecture();
+  std::string GetCpuVendor();
+  int GetCpuFamily();
+  int GetCpuModel();
+  int GetCpuStepping();
+  // Estimated speed of the CPUs, in MHz.
+  int GetMaxCpuSpeed();
+  int GetCurCpuSpeed();
+  // Total amount of physical memory, in bytes.
+  int64 GetMemorySize();
+  // The model name of the machine, e.g. "MacBookAir1,1"
+  std::string GetMachineModel();
+
+  // The gpu identifier
+  struct GpuInfo {
+    GpuInfo() : vendor_id(0), device_id(0) {}
+    std::string device_name;
+    std::string description;
+    int vendor_id;
+    int device_id;
+    std::string driver;
+    std::string driver_version;
+  };
+  bool GetGpuInfo(GpuInfo *info);
+
+ private:
+  int physical_cpus_;
+  int logical_cpus_;
+  Architecture cpu_arch_;
+  std::string cpu_vendor_;
+  int cpu_family_;
+  int cpu_model_;
+  int cpu_stepping_;
+  int cpu_speed_;
+  int64 memory_;
+  std::string machine_model_;
+};
+
+}
+
+#endif  // TALK_BASE_SYSTEMINFO_H__
diff --git a/talk/base/systeminfo_unittest.cc b/talk/base/systeminfo_unittest.cc
new file mode 100644
index 0000000..6df0db5
--- /dev/null
+++ b/talk/base/systeminfo_unittest.cc
@@ -0,0 +1,143 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/systeminfo.h"
+
+#ifdef CPU_X86
+
+// CPUID-based tests only work on X86
+
+// Tests CPUID instruction for Vendor identification.
+TEST(SystemInfoTest, CpuVendorNonEmpty) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuVendor: " << info.GetCpuVendor();
+  EXPECT_FALSE(info.GetCpuVendor().empty());
+}
+
+// Tests Vendor identification is Intel or AMD.
+// See Also http://en.wikipedia.org/wiki/CPUID
+TEST(SystemInfoTest, CpuVendorIntelAMD) {
+  talk_base::SystemInfo info;
+  EXPECT_TRUE(talk_base::string_match(info.GetCpuVendor().c_str(),
+                                      "GenuineIntel") ||
+              talk_base::string_match(info.GetCpuVendor().c_str(),
+                                      "AuthenticAMD"));
+}
+
+#endif // CPU_X86
+
+// Tests MachineModel is set.
+TEST(SystemInfoTest, MachineModelNonEmpty) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "MachineModel: " << info.GetMachineModel();
+  EXPECT_FALSE(info.GetMachineModel().empty());
+}
+
+// Tests maximum cpu clockrate.
+TEST(SystemInfoTest, CpuMaxCpuSpeed) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "MaxCpuSpeed: " << info.GetMaxCpuSpeed();
+  EXPECT_GT(info.GetMaxCpuSpeed(), 0);
+}
+
+// Tests current cpu clockrate.
+TEST(SystemInfoTest, CpuCurCpuSpeed) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "MaxCurSpeed: " << info.GetCurCpuSpeed();
+  EXPECT_GT(info.GetCurCpuSpeed(), 0);
+}
+
+// Tests physical memory size.
+TEST(SystemInfoTest, MemorySize) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "MemorySize: " << info.GetMemorySize();
+  EXPECT_GT(info.GetMemorySize(), -1);
+}
+
+// Tests number of logical cpus available to the system.
+TEST(SystemInfoTest, MaxCpus) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "MaxCpus: " << info.GetMaxCpus();
+  EXPECT_GT(info.GetMaxCpus(), 0);
+}
+
+// Tests number of physical cpus available to the system.
+TEST(SystemInfoTest, MaxPhysicalCpus) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "MaxPhysicalCpus: " << info.GetMaxPhysicalCpus();
+  EXPECT_GT(info.GetMaxPhysicalCpus(), 0);
+  EXPECT_LE(info.GetMaxPhysicalCpus(), info.GetMaxCpus());
+}
+
+// Tests number of logical cpus available to the process.
+TEST(SystemInfoTest, CurCpus) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CurCpus: " << info.GetCurCpus();
+  EXPECT_GT(info.GetCurCpus(), 0);
+  EXPECT_LE(info.GetCurCpus(), info.GetMaxCpus());
+}
+
+#ifdef CPU_X86
+
+// CPU family/model/steeping is only available on X86
+
+// Tests Intel CPU Family identification.
+TEST(SystemInfoTest, CpuFamilyNonZero) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuFamily: " << info.GetCpuFamily();
+  EXPECT_GT(info.GetCpuFamily(), 0);
+}
+
+// Tests Intel CPU Model identification.
+TEST(SystemInfoTest, CpuModelNonZero) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuModel: " << info.GetCpuModel();
+  EXPECT_GT(info.GetCpuModel(), 0);
+}
+
+// Tests Intel CPU Stepping identification.
+TEST(SystemInfoTest, CpuSteppingNonZero) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuStepping: " << info.GetCpuStepping();
+  EXPECT_GT(info.GetCpuStepping(), 0);
+}
+
+#endif // CPU_X86
+
+#if WIN32 && !defined(EXCLUDE_D3D9)
+TEST(SystemInfoTest, GpuInfo) {
+  talk_base::SystemInfo info;
+  talk_base::SystemInfo::GpuInfo gi;
+  EXPECT_TRUE(info.GetGpuInfo(&gi));
+  LOG(LS_INFO) << "GpuDriver: " << gi.driver;
+  EXPECT_FALSE(gi.driver.empty());
+  LOG(LS_INFO) << "GpuDriverVersion: " << gi.driver_version;
+  EXPECT_FALSE(gi.driver_version.empty());
+}
+#endif
diff --git a/talk/base/task.cc b/talk/base/task.cc
index ad17438..c37797c 100644
--- a/talk/base/task.cc
+++ b/talk/base/task.cc
@@ -197,22 +197,15 @@
 }
 
 std::string Task::GetStateName(int state) const {
-  static const std::string STR_BLOCKED("BLOCKED");
-  static const std::string STR_INIT("INIT");
-  static const std::string STR_START("START");
-  static const std::string STR_DONE("DONE");
-  static const std::string STR_ERROR("ERROR");
-  static const std::string STR_RESPONSE("RESPONSE");
-  static const std::string STR_HUH("??");
   switch (state) {
-    case STATE_BLOCKED: return STR_BLOCKED;
-    case STATE_INIT: return STR_INIT;
-    case STATE_START: return STR_START;
-    case STATE_DONE: return STR_DONE;
-    case STATE_ERROR: return STR_ERROR;
-    case STATE_RESPONSE: return STR_RESPONSE;
+    case STATE_BLOCKED: return "BLOCKED";
+    case STATE_INIT: return "INIT";
+    case STATE_START: return "START";
+    case STATE_DONE: return "DONE";
+    case STATE_ERROR: return "ERROR";
+    case STATE_RESPONSE: return "RESPONSE";
   }
-  return STR_HUH;
+  return "??";
 }
 
 int Task::Process(int state) {
diff --git a/talk/base/task_unittest.cc b/talk/base/task_unittest.cc
index 076ff6f..0c4a7a2 100644
--- a/talk/base/task_unittest.cc
+++ b/talk/base/task_unittest.cc
@@ -43,7 +43,7 @@
 #include "talk/base/task.h"
 #include "talk/base/taskrunner.h"
 #include "talk/base/thread.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 
 namespace talk_base {
 
diff --git a/talk/base/testbase64.h b/talk/base/testbase64.h
new file mode 100644
index 0000000..39dd00c
--- /dev/null
+++ b/talk/base/testbase64.h
@@ -0,0 +1,5 @@
+/* This file was generated by googleclient/talk/binary2header.sh */
+
+static unsigned char testbase64[] = {
+0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xe1, 0x0d, 0x07, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x9e, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xbe, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0xc3, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xcc, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xd4, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xdc, 0x01, 0x32, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x3c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x04, 0x02, 0x13, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x02, 0xc4, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x53, 0x4f, 0x4e, 0x59, 0x00, 0x44, 0x53, 0x43, 0x2d, 0x50, 0x32, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x37, 0x2e, 0x30, 0x00, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x33, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x31, 0x30, 0x3a, 0x30, 0x34, 0x00, 0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x58, 0x20, 0x31, 0x30, 0x2e, 0x34, 0x2e, 0x38, 0x00, 0x00, 0x1c, 0x82, 0x9a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x6a, 0x82, 0x9d, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x72, 0x88, 0x22, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x88, 0x27, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x64, 0x00, 0x00, 0x90, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x02, 0x7a, 0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x02, 0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xa2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xaa, 0x92, 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xb2, 0x92, 0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x92, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0f, 0x00, 0x00, 0x92, 0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xba, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0xa0, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0a, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x32, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x30, 0x35, 0x3a, 0x35, 0x32, 0x00, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x32, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x30, 0x35, 0x3a, 0x35, 0x32, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x12, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x1a, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x22, 0x02, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x09, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xed, 0x00, 0x0c, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x5f, 0x43, 0x4d, 0x00, 0x02, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0c, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0c, 0x09, 0x09, 0x0c, 0x11, 0x0b, 0x0a, 0x0b, 0x11, 0x15, 0x0f, 0x0c, 0x0c, 0x0f, 0x15, 0x18, 0x13, 0x13, 0x15, 0x13, 0x13, 0x18, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x0d, 0x0b, 0x0b, 0x0d, 0x0e, 0x0d, 0x10, 0x0e, 0x0e, 0x10, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x07, 0xff, 0xc4, 0x01, 0x3f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x01, 0x04, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x07, 0x06, 0x08, 0x05, 0x03, 0x0c, 0x33, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x05, 0x41, 0x51, 0x61, 0x13, 0x22, 0x71, 0x81, 0x32, 0x06, 0x14, 0x91, 0xa1, 0xb1, 0x42, 0x23, 0x24, 0x15, 0x52, 0xc1, 0x62, 0x33, 0x34, 0x72, 0x82, 0xd1, 0x43, 0x07, 0x25, 0x92, 0x53, 0xf0, 0xe1, 0xf1, 0x63, 0x73, 0x35, 0x16, 0xa2, 0xb2, 0x83, 0x26, 0x44, 0x93, 0x54, 0x64, 0x45, 0xc2, 0xa3, 0x74, 0x36, 0x17, 0xd2, 0x55, 0xe2, 0x65, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x27, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x35, 0x01, 0x00, 0x02, 0x11, 0x03, 0x21, 0x31, 0x12, 0x04, 0x41, 0x51, 0x61, 0x71, 0x22, 0x13, 0x05, 0x32, 0x81, 0x91, 0x14, 0xa1, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xf0, 0x33, 0x24, 0x62, 0xe1, 0x72, 0x82, 0x92, 0x43, 0x53, 0x15, 0x63, 0x73, 0x34, 0xf1, 0x25, 0x06, 0x16, 0xa2, 0xb2, 0x83, 0x07, 0x26, 0x35, 0xc2, 0xd2, 0x44, 0x93, 0x54, 0xa3, 0x17, 0x64, 0x45, 0x55, 0x36, 0x74, 0x65, 0xe2, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf2, 0xed, 0xb2, 0x8d, 0x4d, 0x45, 0xcd, 0x2f, 0x3f, 0x44, 0x68, 0x93, 0xc3, 0x58, 0xc8, 0xf1, 0x1f, 0x8a, 0x33, 0x86, 0xda, 0x58, 0xc1, 0xa0, 0x02, 0x4f, 0xc4, 0xa1, 0x69, 0xa5, 0x9b, 0x5b, 0x4b, 0x84, 0x73, 0xdf, 0xc9, 0x15, 0xf8, 0xe3, 0xd1, 0x0e, 0x07, 0x93, 0xf3, 0xd1, 0x0f, 0x1c, 0x17, 0xef, 0x2e, 0x3b, 0x5b, 0xdc, 0xff, 0x00, 0xdf, 0x42, 0xbf, 0x8f, 0x8e, 0xdc, 0x82, 0xca, 0xd8, 0x37, 0x11, 0xa9, 0x3d, 0x82, 0x69, 0x2b, 0xc4, 0x6d, 0xc9, 0x75, 0x25, 0xbc, 0xf7, 0xec, 0xa1, 0xb5, 0x74, 0x19, 0x5d, 0x2e, 0x8a, 0x9a, 0x4b, 0x89, 0x7d, 0xc4, 0x68, 0xc6, 0xf6, 0xfe, 0xb2, 0xa0, 0x30, 0x1d, 0x60, 0x86, 0x88, 0x8d, 0x49, 0x3e, 0x01, 0x11, 0x20, 0xa3, 0x8c, 0xb9, 0xb1, 0xaa, 0x62, 0xad, 0xbf, 0x18, 0x97, 0x43, 0x47, 0x1d, 0xd2, 0xaf, 0x04, 0xd9, 0xb8, 0xc8, 0x0d, 0x68, 0xe4, 0xf7, 0x3e, 0x48, 0xf1, 0x05, 0xbc, 0x25, 0xaa, 0x07, 0x71, 0xd9, 0x14, 0x78, 0xf6, 0x49, 0xb5, 0x90, 0xfd, 0xa7, 0xc6, 0x14, 0xfd, 0x1b, 0x1c, 0xff, 0x00, 0x4d, 0x8d, 0x2e, 0x73, 0x8c, 0x35, 0xa3, 0x52, 0x4f, 0x92, 0x48, 0xa6, 0x1a, 0x24, 0xb6, 0x2a, 0xfa, 0xa5, 0x9e, 0x60, 0x64, 0x39, 0x94, 0x13, 0xcb, 0x27, 0x73, 0x80, 0xf3, 0x0c, 0xf6, 0xff, 0x00, 0xd2, 0x5a, 0x78, 0xbf, 0x53, 0x71, 0xf6, 0x01, 0x75, 0xb6, 0x97, 0x6a, 0x25, 0xa1, 0xad, 0x1f, 0xf4, 0xb7, 0x23, 0x48, 0xb7, 0x94, 0x84, 0x97, 0x5b, 0xff, 0x00, 0x32, 0xa9, 0xdd, 0xfc, 0xed, 0x9b, 0x7e, 0x0d, 0x9e, 0x52, 0x4a, 0x95, 0x61, 0xff, 0xd0, 0xf3, 0x3b, 0xa7, 0x70, 0xee, 0x01, 0x8f, 0xb9, 0x59, 0xfa, 0x7e, 0xdf, 0xe4, 0xc8, 0xf9, 0x2a, 0xc2, 0x5c, 0x63, 0xc3, 0x54, 0x67, 0x87, 0x6e, 0x10, 0x35, 0x68, 0xd4, 0x79, 0x1e, 0x53, 0x4a, 0xe0, 0xdc, 0xe9, 0xb8, 0x1f, 0x6a, 0xda, 0x6c, 0x25, 0x94, 0x37, 0xb0, 0xd0, 0xb8, 0xad, 0x67, 0xe4, 0x55, 0x8a, 0x5b, 0x8b, 0x82, 0xc0, 0x6f, 0x76, 0x80, 0x34, 0x49, 0x05, 0x2e, 0x9e, 0xc6, 0x1c, 0x66, 0x31, 0xba, 0x10, 0x23, 0xe0, 0xaf, 0xe1, 0x61, 0x53, 0x43, 0x8d, 0x81, 0xb3, 0x67, 0xef, 0x9e, 0x49, 0x2a, 0x12, 0x6c, 0xb6, 0x63, 0x1a, 0x0c, 0x31, 0xba, 0x55, 0xcd, 0xac, 0xfa, 0x8e, 0xdf, 0x91, 0x6e, 0x91, 0xd9, 0xb3, 0xc9, 0x73, 0x90, 0x7a, 0xab, 0x6a, 0xc2, 0xa4, 0x60, 0xe2, 0x8f, 0xd2, 0x38, 0x03, 0x7d, 0x9e, 0x0d, 0xff, 0x00, 0xcc, 0xd6, 0xd3, 0x6b, 0x71, 0x67, 0xd2, 0x3e, 0x64, 0x72, 0xab, 0xdb, 0x8d, 0x54, 0x39, 0xc5, 0x83, 0x6b, 0x3d, 0xee, 0x2e, 0xd4, 0x92, 0x3c, 0x4a, 0x56, 0xba, 0xb4, 0x79, 0x5c, 0xf7, 0xb2, 0x96, 0x6c, 0x8d, 0xaf, 0x80, 0x48, 0x3c, 0xf0, 0xb2, 0x1f, 0x63, 0x9c, 0xe9, 0x3f, 0x24, 0x5c, 0xdb, 0xdd, 0x76, 0x43, 0xde, 0xfd, 0x5c, 0xe3, 0x24, 0xfc, 0x50, 0x00, 0x93, 0x0a, 0x78, 0x8a, 0x0d, 0x49, 0xca, 0xcf, 0x93, 0x63, 0x1b, 0x7d, 0xd7, 0x57, 0x50, 0xd5, 0xef, 0x70, 0x6b, 0x4f, 0xc7, 0x45, 0xdb, 0x74, 0x9e, 0x8d, 0x5e, 0x33, 0x83, 0xd8, 0x37, 0xdd, 0xc3, 0xac, 0x3d, 0xbf, 0x92, 0xc5, 0x5b, 0xea, 0xbf, 0xd5, 0x62, 0xc0, 0xdc, 0xbc, 0xbd, 0x2d, 0x22, 0x5a, 0xcf, 0xdd, 0x69, 0xff, 0x00, 0xd1, 0x8e, 0x5d, 0xa5, 0x38, 0xb5, 0xb0, 0x00, 0xc6, 0xc4, 0x24, 0x4a, 0xd6, 0x8d, 0x18, 0x04, 0x49, 0x88, 0x9e, 0x55, 0xd6, 0x61, 0xb0, 0xc1, 0x70, 0x32, 0xdd, 0x3c, 0x95, 0xda, 0xf1, 0xfe, 0xf5, 0x62, 0xbc, 0x76, 0x8e, 0x75, 0x28, 0x02, 0xa2, 0xe7, 0x7d, 0x92, 0xb9, 0x84, 0x96, 0x96, 0xda, 0xf7, 0x70, 0x12, 0x4e, 0x5a, 0xff, 0x00, 0xff, 0xd1, 0xf3, 0x7a, 0x21, 0xaf, 0xde, 0xef, 0xa2, 0x22, 0x55, 0xfc, 0x5a, 0xbd, 0x42, 0xfb, 0x08, 0xfa, 0x67, 0x4f, 0x82, 0xcd, 0x6d, 0x85, 0xc0, 0x56, 0x3b, 0x90, 0xb7, 0xf0, 0x2a, 0x0e, 0x63, 0x58, 0x3b, 0xf2, 0xa3, 0x9e, 0x8c, 0xb8, 0x86, 0xbe, 0x49, 0xf1, 0x2c, 0x0c, 0x86, 0xb4, 0x4c, 0x69, 0xe4, 0xaf, 0x6e, 0xcc, 0x6b, 0x7d, 0x46, 0xb3, 0x70, 0xec, 0x38, 0x51, 0x7d, 0x02, 0x8a, 0xc7, 0xa6, 0xd9, 0x20, 0x68, 0x0f, 0x8f, 0x8a, 0xcf, 0xc9, 0xc2, 0xea, 0x59, 0x5b, 0x48, 0xb0, 0x91, 0xae, 0xe6, 0xc9, 0x03, 0xc9, 0x30, 0x51, 0x66, 0xd4, 0x0d, 0xad, 0xbd, 0x5f, 0x53, 0xcc, 0x6b, 0xb6, 0x90, 0x5a, 0x3b, 0x83, 0x0b, 0x43, 0x17, 0x31, 0xd6, 0xc3, 0x6e, 0x12, 0x3b, 0x79, 0xac, 0xc1, 0x89, 0x47, 0xd9, 0xe8, 0x63, 0x98, 0x45, 0xed, 0x6c, 0x5a, 0xf1, 0xa0, 0x27, 0xc5, 0x5b, 0xc3, 0x6f, 0xa6, 0xe0, 0x1c, 0x7d, 0xb3, 0xa2, 0x69, 0x34, 0x7b, 0xae, 0x1a, 0x8d, 0x45, 0x17, 0x9d, 0xeb, 0xfd, 0x21, 0xd8, 0xb9, 0xae, 0xb5, 0x80, 0xbb, 0x1e, 0xd2, 0x5c, 0xd7, 0x78, 0x13, 0xf9, 0xae, 0x4b, 0xea, 0xc7, 0x4a, 0x39, 0xbd, 0x55, 0xb3, 0xed, 0x66, 0x38, 0xf5, 0x09, 0x22, 0x41, 0x23, 0xe8, 0x37, 0xfb, 0x4b, 0xa1, 0xeb, 0xd6, 0xfe, 0x88, 0x31, 0xbf, 0x41, 0xc0, 0xee, 0xd2, 0x74, 0x02, 0x78, 0x53, 0xfa, 0x97, 0x43, 0x19, 0x85, 0x65, 0xff, 0x00, 0x9d, 0x71, 0x33, 0xe4, 0x1a, 0x7d, 0x8d, 0x53, 0x42, 0x56, 0x35, 0x6b, 0xe5, 0x80, 0x06, 0xc7, 0x57, 0xa7, 0xc4, 0xa9, 0xdb, 0xb6, 0x81, 0x1f, 0xeb, 0xd9, 0x69, 0x56, 0xc2, 0xd0, 0x00, 0xe5, 0x55, 0xc0, 0x12, 0xc2, 0xd7, 0x4e, 0xa2, 0x5a, 0x7c, 0x0a, 0xd0, 0x63, 0x9a, 0xd1, 0xaf, 0xd2, 0xe2, 0x3c, 0x12, 0x62, 0x66, 0xc6, 0x42, 0x23, 0x5a, 0x49, 0x8f, 0x10, 0xa2, 0xd2, 0x3e, 0x28, 0x9d, 0xc4, 0x88, 0x09, 0x29, 0x16, 0xc3, 0x3c, 0x24, 0x8d, 0xe6, 0x92, 0x72, 0x1f, 0xff, 0xd2, 0xf3, 0xbb, 0xb0, 0xfe, 0xcb, 0x99, 0xe9, 0xce, 0xf6, 0x88, 0x2d, 0x77, 0x91, 0x5b, 0x3d, 0x3d, 0xd0, 0xe6, 0x90, 0xa9, 0x65, 0x57, 0x38, 0x95, 0xdd, 0xcb, 0x9a, 0x7d, 0xce, 0xf2, 0x3f, 0x44, 0x23, 0x60, 0x58, 0x76, 0xe9, 0xca, 0x8c, 0xea, 0x1b, 0x31, 0x02, 0x32, 0x23, 0xea, 0xee, 0xb1, 0xcd, 0xb0, 0xc7, 0x87, 0x74, 0x7a, 0xeb, 0x70, 0x1a, 0x71, 0xe1, 0xfe, 0xe4, 0x1c, 0x1d, 0xae, 0xe5, 0x69, 0xd8, 0xfa, 0x99, 0x50, 0x0d, 0x1a, 0xf7, 0x2a, 0x3a, 0x0c, 0xf4, 0x1a, 0x8e, 0xc7, 0x27, 0x5d, 0xbf, 0x18, 0x41, 0xdc, 0xc2, 0xf0, 0x7f, 0x74, 0xf6, 0x3a, 0x22, 0x66, 0xdb, 0x68, 0xc6, 0x80, 0x48, 0x6b, 0x88, 0x06, 0x39, 0x0d, 0xee, 0xaa, 0x1f, 0xb3, 0xd5, 0x1b, 0x83, 0xd8, 0x3b, 0x38, 0x8f, 0x69, 0xfe, 0xdf, 0xd1, 0x4d, 0x29, 0xa1, 0x4c, 0x7a, 0xf4, 0xbf, 0xa7, 0x92, 0xcf, 0xa5, 0x20, 0x08, 0xf3, 0xf6, 0xff, 0x00, 0x15, 0xbb, 0xd1, 0x31, 0xd9, 0x5e, 0x3d, 0x75, 0x56, 0x36, 0x88, 0x00, 0x81, 0xe0, 0x16, 0x5e, 0x55, 0x74, 0x3f, 0x00, 0x9d, 0xe0, 0xcc, 0x69, 0xe7, 0x3a, 0x2d, 0xbe, 0x90, 0x00, 0xa9, 0xae, 0xef, 0x1f, 0x95, 0x4b, 0x0d, 0x9a, 0xdc, 0xc7, 0x45, 0xfe, 0xb1, 0x7d, 0x60, 0xa7, 0xa1, 0xe0, 0x1f, 0x4e, 0x1d, 0x99, 0x69, 0x02, 0x9a, 0xcf, 0x1f, 0xca, 0x7b, 0xbf, 0x90, 0xc5, 0xc2, 0xb3, 0xeb, 0x57, 0xd6, 0x03, 0x6b, 0xae, 0x39, 0xb6, 0x82, 0xe3, 0x31, 0xa1, 0x68, 0xf2, 0x6b, 0x5c, 0x12, 0xfa, 0xe1, 0x91, 0x66, 0x47, 0x5d, 0xb8, 0x3b, 0x4f, 0x44, 0x36, 0xb6, 0x8f, 0x28, 0xdd, 0xff, 0x00, 0x7e, 0x46, 0xab, 0x12, 0x2b, 0x65, 0x55, 0x32, 0xa7, 0x62, 0xb6, 0xbd, 0xf7, 0x64, 0x10, 0xdb, 0x03, 0x9f, 0x1b, 0x9e, 0xc7, 0xd9, 0xb8, 0x3b, 0x1f, 0x67, 0xf3, 0x6c, 0x52, 0x80, 0xd7, 0x7d, 0x0f, 0xea, 0x7f, 0x5d, 0x1d, 0x67, 0xa6, 0x0b, 0x1e, 0x47, 0xda, 0x69, 0x3b, 0x2e, 0x03, 0xc7, 0xf3, 0x5f, 0x1f, 0xf0, 0x8b, 0xa1, 0x02, 0x46, 0xba, 0x79, 0xaf, 0x32, 0xff, 0x00, 0x16, 0xad, 0xca, 0x1d, 0x57, 0x2a, 0xdc, 0x79, 0x18, 0x41, 0xb0, 0xf6, 0x9e, 0xe4, 0x9f, 0xd0, 0x8f, 0xeb, 0x31, 0xab, 0xd2, 0x83, 0xa4, 0xcb, 0x8c, 0xb8, 0xa0, 0x42, 0x12, 0x7b, 0x67, 0x9f, 0x2f, 0xf5, 0x09, 0x26, 0x96, 0xc4, 0xce, 0xa9, 0x20, 0xa7, 0xff, 0xd3, 0xf3, 0x2f, 0xb4, 0x5d, 0xe9, 0x0a, 0xb7, 0x9f, 0x4c, 0x19, 0xdb, 0x3a, 0x2d, 0x5e, 0x94, 0xfd, 0xc4, 0xb7, 0xc5, 0x62, 0xf9, 0x2b, 0xfd, 0x2e, 0xe3, 0x5d, 0xe0, 0x7c, 0x13, 0x48, 0xd1, 0x92, 0x12, 0xa9, 0x0b, 0x7a, 0xbc, 0x2d, 0xc2, 0x7f, 0x92, 0x60, 0xab, 0x4e, 0x79, 0x2e, 0x00, 0xf0, 0xaa, 0xe1, 0xda, 0x3d, 0x43, 0xfc, 0xad, 0x55, 0xbb, 0x80, 0x79, 0x81, 0xa0, 0xe6, 0x54, 0x32, 0x6d, 0x02, 0xbe, 0xf3, 0x61, 0x81, 0xa8, 0x44, 0x14, 0x03, 0x59, 0x0e, 0x1c, 0xf6, 0x1f, 0xdc, 0xb2, 0xec, 0xa3, 0x23, 0x77, 0xe8, 0x6e, 0x70, 0xf2, 0x25, 0x1f, 0x1f, 0x17, 0xa9, 0x6d, 0x71, 0x36, 0x97, 0x47, 0x00, 0xa4, 0x02, 0xe0, 0x2c, 0x7c, 0xc1, 0xab, 0xd5, 0x31, 0x85, 0x35, 0xd4, 0xe6, 0x13, 0x02, 0xd6, 0x4b, 0x67, 0x48, 0x2b, 0xa9, 0xe9, 0x2e, 0x02, 0xb6, 0x4f, 0x82, 0xe5, 0x7a, 0x95, 0x19, 0xc6, 0x87, 0x3d, 0xfb, 0xa2, 0xb8, 0x79, 0x1e, 0x4d, 0x3b, 0x96, 0xcf, 0x4f, 0xbd, 0xcd, 0xa2, 0xa2, 0x1f, 0xa0, 0x82, 0xd3, 0xfc, 0x97, 0x05, 0x24, 0x36, 0x6b, 0xf3, 0x31, 0xa2, 0x35, 0x79, 0xef, 0xad, 0xf8, 0xae, 0xaf, 0xaf, 0xd8, 0xf2, 0xd8, 0x6d, 0xed, 0x6b, 0xda, 0x7b, 0x18, 0x1b, 0x5d, 0xff, 0x00, 0x52, 0xb1, 0x6d, 0xf0, 0x81, 0x31, 0xca, 0xf4, 0x6e, 0xb1, 0x80, 0xce, 0xb1, 0x84, 0xc0, 0x21, 0xb7, 0xd6, 0x77, 0x31, 0xd1, 0x27, 0xc1, 0xcd, 0xfe, 0xd2, 0xe3, 0xec, 0xe8, 0x1d, 0x45, 0x96, 0xb0, 0x9a, 0xb7, 0x87, 0x3f, 0x68, 0x2d, 0xf7, 0x01, 0x1f, 0xbe, 0xd1, 0xf4, 0x7f, 0xb4, 0xa4, 0x0d, 0x77, 0xbb, 0xfa, 0x8f, 0x80, 0x3a, 0x7f, 0x43, 0xaa, 0xe2, 0xdf, 0xd2, 0x65, 0x7e, 0x95, 0xe4, 0x0f, 0x1f, 0xa1, 0xfe, 0x6b, 0x16, 0x9f, 0x52, 0xfa, 0xc1, 0xd3, 0xba, 0x6d, 0x26, 0xdc, 0xac, 0x86, 0xd4, 0xd9, 0x0d, 0x31, 0x2e, 0x74, 0x9e, 0xdb, 0x59, 0x2e, 0x55, 0xe8, 0xc9, 0xb2, 0x96, 0xd5, 0x4b, 0x9f, 0xb8, 0x6d, 0xda, 0x1c, 0x04, 0x09, 0x03, 0xfe, 0x8a, 0xc6, 0xfa, 0xd3, 0xf5, 0x6a, 0xbe, 0xbb, 0x5b, 0x2e, 0xc6, 0xb5, 0x94, 0xe6, 0xd5, 0x20, 0x97, 0x7d, 0x1b, 0x1b, 0xf9, 0xad, 0x7c, 0x7d, 0x17, 0xb7, 0xf3, 0x1e, 0x92, 0x1b, 0x7f, 0xf8, 0xe0, 0x7d, 0x59, 0xdd, 0xfd, 0x32, 0xd8, 0x8f, 0xa5, 0xe8, 0x3a, 0x12, 0x5c, 0x3f, 0xfc, 0xc4, 0xfa, 0xc3, 0xb3, 0x77, 0xa7, 0x56, 0xed, 0xdb, 0x76, 0x7a, 0x8d, 0xdd, 0x1f, 0xbf, 0xfd, 0x44, 0x92, 0x56, 0x8f, 0xff, 0xd4, 0xf2, 0xe8, 0x86, 0x17, 0x1e, 0xfa, 0x04, 0x56, 0x4b, 0x43, 0x6c, 0x6f, 0x2d, 0xe5, 0x46, 0x01, 0x64, 0x2b, 0x14, 0x32, 0x5b, 0xb4, 0xa0, 0x52, 0x1d, 0xde, 0x9b, 0x94, 0xdb, 0xab, 0x6b, 0x81, 0xf7, 0x05, 0xb0, 0xd7, 0x07, 0xb2, 0x27, 0x55, 0xc6, 0x57, 0x65, 0xd8, 0x76, 0x6e, 0x64, 0xed, 0xee, 0x16, 0xce, 0x27, 0x57, 0x63, 0xda, 0x0c, 0xc2, 0x8e, 0x51, 0x67, 0x84, 0xfa, 0x1d, 0xdd, 0x62, 0xc7, 0x07, 0xe9, 0xf7, 0xa3, 0xd6, 0x6c, 0x02, 0x41, 0x55, 0x31, 0xf3, 0x2b, 0xb3, 0xba, 0x2b, 0x2e, 0x68, 0x24, 0x1d, 0x47, 0x64, 0xca, 0xa6, 0x50, 0x41, 0x65, 0x90, 0x6c, 0xb1, 0xa5, 0xae, 0x33, 0x23, 0x51, 0xe4, 0xab, 0x7d, 0x5d, 0xcb, 0xb6, 0xcc, 0x37, 0xd0, 0x40, 0x73, 0x71, 0xde, 0x58, 0x09, 0xe7, 0x6f, 0x2c, 0x44, 0xc9, 0xc9, 0xae, 0xba, 0x9d, 0x63, 0x88, 0x01, 0xa0, 0x95, 0x9d, 0xf5, 0x3f, 0x2a, 0xe6, 0x67, 0xdb, 0x50, 0x83, 0x55, 0xad, 0x36, 0x3e, 0x78, 0x10, 0x74, 0x77, 0xfd, 0x2d, 0xaa, 0x4c, 0x7d, 0x58, 0x73, 0x91, 0xa0, 0x0f, 0x51, 0x45, 0xb7, 0x33, 0xdd, 0x58, 0x69, 0x1d, 0xd8, 0x0c, 0x9f, 0x96, 0x88, 0x19, 0x99, 0x19, 0xac, 0xcf, 0xa3, 0xd2, 0xad, 0xb5, 0xdb, 0x76, 0x8f, 0xad, 0xc4, 0xea, 0xcf, 0xdf, 0x7e, 0xdf, 0xdd, 0xfc, 0xd5, 0xa3, 0x5e, 0x43, 0x2b, 0x6b, 0xb2, 0xad, 0x3b, 0x6a, 0xa4, 0x13, 0xa7, 0x04, 0xac, 0x7a, 0x6f, 0xb3, 0x23, 0x26, 0xcc, 0xfb, 0xb4, 0x75, 0x8e, 0x01, 0x83, 0xf7, 0x58, 0x3e, 0x8b, 0x53, 0xa7, 0x2a, 0x1a, 0x31, 0x42, 0x36, 0x5d, 0x4c, 0x9a, 0xf2, 0xdc, 0xc6, 0xfe, 0x98, 0xb4, 0x34, 0xcb, 0x48, 0x0a, 0x8f, 0xdb, 0xb2, 0xeb, 0x76, 0xd6, 0x07, 0x5c, 0x59, 0xc9, 0x64, 0x8f, 0x93, 0xa7, 0x73, 0x16, 0x83, 0xaf, 0x0e, 0xa4, 0x33, 0xef, 0x50, 0xc5, 0x0c, 0xda, 0x59, 0x10, 0x06, 0x8a, 0x2e, 0x29, 0x0e, 0xac, 0xc2, 0x31, 0x3d, 0x36, 0x69, 0x7e, 0xd6, 0xcc, 0xf5, 0x3d, 0x6f, 0xb3, 0xeb, 0x1b, 0x76, 0xef, 0x3b, 0xa3, 0xfa, 0xc9, 0x2b, 0x5f, 0x66, 0x6f, 0xa9, 0x1e, 0x73, 0xf2, 0x49, 0x2e, 0x39, 0xf7, 0x4f, 0xb7, 0x8d, 0xff, 0xd5, 0xf3, 0x26, 0xfe, 0x0a, 0xc5, 0x1b, 0xa7, 0xcb, 0xb2, 0xcf, 0x49, 0x03, 0xb2, 0x46, 0xee, 0xd9, 0xd9, 0xb3, 0xf4, 0x9f, 0x25, 0x4a, 0xdf, 0x4b, 0x77, 0xe8, 0x27, 0xd4, 0xef, 0x1c, 0x2a, 0x29, 0x26, 0xc5, 0x7c, 0x9d, 0x6c, 0x7f, 0xb7, 0x6e, 0x1b, 0x26, 0x7f, 0x05, 0xa3, 0xfe, 0x53, 0x8d, 0x62, 0x57, 0x30, 0x92, 0x12, 0xfa, 0x2f, 0x86, 0xdf, 0xa4, 0xec, 0x67, 0xfe, 0xd0, 0xf4, 0xff, 0x00, 0x4d, 0xfc, 0xdf, 0x78, 0xe1, 0x68, 0x7d, 0x54, 0x99, 0xbf, 0x6f, 0xf3, 0xbe, 0xdf, 0x8e, 0xdd, 0x7f, 0xef, 0xeb, 0x97, 0x49, 0x3e, 0x3b, 0x7f, 0x06, 0x2c, 0x9f, 0x37, 0x5f, 0xf0, 0x9f, 0x4c, 0xeb, 0x7b, 0xbf, 0x67, 0x55, 0xe8, 0xff, 0x00, 0x31, 0xbc, 0x7a, 0x9e, 0x31, 0xdb, 0xfe, 0x92, 0xae, 0x37, 0x7a, 0x4d, 0xdb, 0xe2, 0x17, 0x9d, 0xa4, 0xa3, 0xc9, 0xba, 0xfc, 0x7b, 0x7d, 0x5f, 0x52, 0xa7, 0x7e, 0xd1, 0x28, 0xf8, 0xf3, 0xb0, 0xc7, 0x32, 0xbc, 0x99, 0x24, 0xc5, 0xe3, 0xab, 0xeb, 0x1f, 0xa4, 0xf5, 0xfc, 0xe1, 0x25, 0xe4, 0xe9, 0x24, 0x97, 0xff, 0xd9, 0xff, 0xed, 0x2e, 0x1c, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x33, 0x2e, 0x30, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x1c, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, 0x1c, 0x02, 0x78, 0x00, 0x1f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfb, 0x09, 0xa6, 0xbd, 0x07, 0x4c, 0x2a, 0x36, 0x9d, 0x8f, 0xe2, 0xcc, 0x57, 0xa9, 0xac, 0x85, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xea, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xb0, 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x55, 0x54, 0x46, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x0a, 0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45, 0x20, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x22, 0x2d, 0x2f, 0x2f, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x2f, 0x44, 0x54, 0x44, 0x20, 0x50, 0x4c, 0x49, 0x53, 0x54, 0x20, 0x31, 0x2e, 0x30, 0x2f, 0x2f, 0x45, 0x4e, 0x22, 0x20, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x54, 0x44, 0x73, 0x2f, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x2d, 0x31, 0x2e, 0x30, 0x2e, 0x64, 0x74, 0x64, 0x22, 0x3e, 0x0a, 0x3c, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x3e, 0x0a, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x48, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x48, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x32, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x31, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x32, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x31, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x73, 0x75, 0x62, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x61, 0x70, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x33, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x37, 0x36, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x37, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x39, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x6e, 0x61, 0x2d, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x54, 0x31, 0x37, 0x3a, 0x34, 0x39, 0x3a, 0x33, 0x36, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x33, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x37, 0x36, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x37, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x39, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x70, 0x64, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x70, 0x64, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x55, 0x53, 0x20, 0x4c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x54, 0x31, 0x37, 0x3a, 0x34, 0x39, 0x3a, 0x33, 0x36, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x30, 0x30, 0x2e, 0x32, 0x30, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2f, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x30, 0x30, 0x2e, 0x32, 0x30, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2f, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x3c, 0x2f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x3e, 0x0a, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0xde, 0x02, 0x40, 0xff, 0xee, 0xff, 0xee, 0x03, 0x06, 0x02, 0x52, 0x03, 0x67, 0x05, 0x28, 0x03, 0xfc, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd8, 0x02, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x01, 0x7f, 0xff, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x08, 0x00, 0x19, 0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x27, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x2f, 0x66, 0x66, 0x00, 0x01, 0x00, 0x6c, 0x66, 0x66, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2f, 0x66, 0x66, 0x00, 0x01, 0x00, 0xa1, 0x99, 0x9a, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x03, 0x45, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x08, 0x00, 0x44, 0x00, 0x53, 0x00, 0x43, 0x00, 0x30, 0x00, 0x32, 0x00, 0x33, 0x00, 0x32, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x63, 0x74, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x70, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x66, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x74, 0x6f, 0x6d, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x52, 0x67, 0x68, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x06, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x56, 0x6c, 0x4c, 0x73, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x07, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x49, 0x44, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x44, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0c, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x6f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x00, 0x00, 0x00, 0x00, 0x54, 0x79, 0x70, 0x65, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0a, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x00, 0x00, 0x00, 0x00, 0x49, 0x6d, 0x67, 0x20, 0x00, 0x00, 0x00, 0x06, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x63, 0x74, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x70, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x66, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x74, 0x6f, 0x6d, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x52, 0x67, 0x68, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, 0x6c, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6c, 0x6c, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x73, 0x67, 0x65, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x61, 0x6c, 0x74, 0x54, 0x61, 0x67, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x63, 0x65, 0x6c, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x49, 0x73, 0x48, 0x54, 0x4d, 0x4c, 0x62, 0x6f, 0x6f, 0x6c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x63, 0x65, 0x6c, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x68, 0x6f, 0x72, 0x7a, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0f, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x48, 0x6f, 0x72, 0x7a, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x00, 0x00, 0x00, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x00, 0x00, 0x09, 0x76, 0x65, 0x72, 0x74, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0f, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x74, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x00, 0x00, 0x00, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x00, 0x00, 0x0b, 0x62, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x11, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x42, 0x47, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x09, 0x74, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x6c, 0x65, 0x66, 0x74, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x72, 0x69, 0x67, 0x68, 0x74, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x09, 0xf9, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0x09, 0xdd, 0x00, 0x18, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xed, 0x00, 0x0c, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x5f, 0x43, 0x4d, 0x00, 0x02, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0c, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0c, 0x09, 0x09, 0x0c, 0x11, 0x0b, 0x0a, 0x0b, 0x11, 0x15, 0x0f, 0x0c, 0x0c, 0x0f, 0x15, 0x18, 0x13, 0x13, 0x15, 0x13, 0x13, 0x18, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x0d, 0x0b, 0x0b, 0x0d, 0x0e, 0x0d, 0x10, 0x0e, 0x0e, 0x10, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x07, 0xff, 0xc4, 0x01, 0x3f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x01, 0x04, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x07, 0x06, 0x08, 0x05, 0x03, 0x0c, 0x33, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x05, 0x41, 0x51, 0x61, 0x13, 0x22, 0x71, 0x81, 0x32, 0x06, 0x14, 0x91, 0xa1, 0xb1, 0x42, 0x23, 0x24, 0x15, 0x52, 0xc1, 0x62, 0x33, 0x34, 0x72, 0x82, 0xd1, 0x43, 0x07, 0x25, 0x92, 0x53, 0xf0, 0xe1, 0xf1, 0x63, 0x73, 0x35, 0x16, 0xa2, 0xb2, 0x83, 0x26, 0x44, 0x93, 0x54, 0x64, 0x45, 0xc2, 0xa3, 0x74, 0x36, 0x17, 0xd2, 0x55, 0xe2, 0x65, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x27, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x35, 0x01, 0x00, 0x02, 0x11, 0x03, 0x21, 0x31, 0x12, 0x04, 0x41, 0x51, 0x61, 0x71, 0x22, 0x13, 0x05, 0x32, 0x81, 0x91, 0x14, 0xa1, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xf0, 0x33, 0x24, 0x62, 0xe1, 0x72, 0x82, 0x92, 0x43, 0x53, 0x15, 0x63, 0x73, 0x34, 0xf1, 0x25, 0x06, 0x16, 0xa2, 0xb2, 0x83, 0x07, 0x26, 0x35, 0xc2, 0xd2, 0x44, 0x93, 0x54, 0xa3, 0x17, 0x64, 0x45, 0x55, 0x36, 0x74, 0x65, 0xe2, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf2, 0xed, 0xb2, 0x8d, 0x4d, 0x45, 0xcd, 0x2f, 0x3f, 0x44, 0x68, 0x93, 0xc3, 0x58, 0xc8, 0xf1, 0x1f, 0x8a, 0x33, 0x86, 0xda, 0x58, 0xc1, 0xa0, 0x02, 0x4f, 0xc4, 0xa1, 0x69, 0xa5, 0x9b, 0x5b, 0x4b, 0x84, 0x73, 0xdf, 0xc9, 0x15, 0xf8, 0xe3, 0xd1, 0x0e, 0x07, 0x93, 0xf3, 0xd1, 0x0f, 0x1c, 0x17, 0xef, 0x2e, 0x3b, 0x5b, 0xdc, 0xff, 0x00, 0xdf, 0x42, 0xbf, 0x8f, 0x8e, 0xdc, 0x82, 0xca, 0xd8, 0x37, 0x11, 0xa9, 0x3d, 0x82, 0x69, 0x2b, 0xc4, 0x6d, 0xc9, 0x75, 0x25, 0xbc, 0xf7, 0xec, 0xa1, 0xb5, 0x74, 0x19, 0x5d, 0x2e, 0x8a, 0x9a, 0x4b, 0x89, 0x7d, 0xc4, 0x68, 0xc6, 0xf6, 0xfe, 0xb2, 0xa0, 0x30, 0x1d, 0x60, 0x86, 0x88, 0x8d, 0x49, 0x3e, 0x01, 0x11, 0x20, 0xa3, 0x8c, 0xb9, 0xb1, 0xaa, 0x62, 0xad, 0xbf, 0x18, 0x97, 0x43, 0x47, 0x1d, 0xd2, 0xaf, 0x04, 0xd9, 0xb8, 0xc8, 0x0d, 0x68, 0xe4, 0xf7, 0x3e, 0x48, 0xf1, 0x05, 0xbc, 0x25, 0xaa, 0x07, 0x71, 0xd9, 0x14, 0x78, 0xf6, 0x49, 0xb5, 0x90, 0xfd, 0xa7, 0xc6, 0x14, 0xfd, 0x1b, 0x1c, 0xff, 0x00, 0x4d, 0x8d, 0x2e, 0x73, 0x8c, 0x35, 0xa3, 0x52, 0x4f, 0x92, 0x48, 0xa6, 0x1a, 0x24, 0xb6, 0x2a, 0xfa, 0xa5, 0x9e, 0x60, 0x64, 0x39, 0x94, 0x13, 0xcb, 0x27, 0x73, 0x80, 0xf3, 0x0c, 0xf6, 0xff, 0x00, 0xd2, 0x5a, 0x78, 0xbf, 0x53, 0x71, 0xf6, 0x01, 0x75, 0xb6, 0x97, 0x6a, 0x25, 0xa1, 0xad, 0x1f, 0xf4, 0xb7, 0x23, 0x48, 0xb7, 0x94, 0x84, 0x97, 0x5b, 0xff, 0x00, 0x32, 0xa9, 0xdd, 0xfc, 0xed, 0x9b, 0x7e, 0x0d, 0x9e, 0x52, 0x4a, 0x95, 0x61, 0xff, 0xd0, 0xf3, 0x3b, 0xa7, 0x70, 0xee, 0x01, 0x8f, 0xb9, 0x59, 0xfa, 0x7e, 0xdf, 0xe4, 0xc8, 0xf9, 0x2a, 0xc2, 0x5c, 0x63, 0xc3, 0x54, 0x67, 0x87, 0x6e, 0x10, 0x35, 0x68, 0xd4, 0x79, 0x1e, 0x53, 0x4a, 0xe0, 0xdc, 0xe9, 0xb8, 0x1f, 0x6a, 0xda, 0x6c, 0x25, 0x94, 0x37, 0xb0, 0xd0, 0xb8, 0xad, 0x67, 0xe4, 0x55, 0x8a, 0x5b, 0x8b, 0x82, 0xc0, 0x6f, 0x76, 0x80, 0x34, 0x49, 0x05, 0x2e, 0x9e, 0xc6, 0x1c, 0x66, 0x31, 0xba, 0x10, 0x23, 0xe0, 0xaf, 0xe1, 0x61, 0x53, 0x43, 0x8d, 0x81, 0xb3, 0x67, 0xef, 0x9e, 0x49, 0x2a, 0x12, 0x6c, 0xb6, 0x63, 0x1a, 0x0c, 0x31, 0xba, 0x55, 0xcd, 0xac, 0xfa, 0x8e, 0xdf, 0x91, 0x6e, 0x91, 0xd9, 0xb3, 0xc9, 0x73, 0x90, 0x7a, 0xab, 0x6a, 0xc2, 0xa4, 0x60, 0xe2, 0x8f, 0xd2, 0x38, 0x03, 0x7d, 0x9e, 0x0d, 0xff, 0x00, 0xcc, 0xd6, 0xd3, 0x6b, 0x71, 0x67, 0xd2, 0x3e, 0x64, 0x72, 0xab, 0xdb, 0x8d, 0x54, 0x39, 0xc5, 0x83, 0x6b, 0x3d, 0xee, 0x2e, 0xd4, 0x92, 0x3c, 0x4a, 0x56, 0xba, 0xb4, 0x79, 0x5c, 0xf7, 0xb2, 0x96, 0x6c, 0x8d, 0xaf, 0x80, 0x48, 0x3c, 0xf0, 0xb2, 0x1f, 0x63, 0x9c, 0xe9, 0x3f, 0x24, 0x5c, 0xdb, 0xdd, 0x76, 0x43, 0xde, 0xfd, 0x5c, 0xe3, 0x24, 0xfc, 0x50, 0x00, 0x93, 0x0a, 0x78, 0x8a, 0x0d, 0x49, 0xca, 0xcf, 0x93, 0x63, 0x1b, 0x7d, 0xd7, 0x57, 0x50, 0xd5, 0xef, 0x70, 0x6b, 0x4f, 0xc7, 0x45, 0xdb, 0x74, 0x9e, 0x8d, 0x5e, 0x33, 0x83, 0xd8, 0x37, 0xdd, 0xc3, 0xac, 0x3d, 0xbf, 0x92, 0xc5, 0x5b, 0xea, 0xbf, 0xd5, 0x62, 0xc0, 0xdc, 0xbc, 0xbd, 0x2d, 0x22, 0x5a, 0xcf, 0xdd, 0x69, 0xff, 0x00, 0xd1, 0x8e, 0x5d, 0xa5, 0x38, 0xb5, 0xb0, 0x00, 0xc6, 0xc4, 0x24, 0x4a, 0xd6, 0x8d, 0x18, 0x04, 0x49, 0x88, 0x9e, 0x55, 0xd6, 0x61, 0xb0, 0xc1, 0x70, 0x32, 0xdd, 0x3c, 0x95, 0xda, 0xf1, 0xfe, 0xf5, 0x62, 0xbc, 0x76, 0x8e, 0x75, 0x28, 0x02, 0xa2, 0xe7, 0x7d, 0x92, 0xb9, 0x84, 0x96, 0x96, 0xda, 0xf7, 0x70, 0x12, 0x4e, 0x5a, 0xff, 0x00, 0xff, 0xd1, 0xf3, 0x7a, 0x21, 0xaf, 0xde, 0xef, 0xa2, 0x22, 0x55, 0xfc, 0x5a, 0xbd, 0x42, 0xfb, 0x08, 0xfa, 0x67, 0x4f, 0x82, 0xcd, 0x6d, 0x85, 0xc0, 0x56, 0x3b, 0x90, 0xb7, 0xf0, 0x2a, 0x0e, 0x63, 0x58, 0x3b, 0xf2, 0xa3, 0x9e, 0x8c, 0xb8, 0x86, 0xbe, 0x49, 0xf1, 0x2c, 0x0c, 0x86, 0xb4, 0x4c, 0x69, 0xe4, 0xaf, 0x6e, 0xcc, 0x6b, 0x7d, 0x46, 0xb3, 0x70, 0xec, 0x38, 0x51, 0x7d, 0x02, 0x8a, 0xc7, 0xa6, 0xd9, 0x20, 0x68, 0x0f, 0x8f, 0x8a, 0xcf, 0xc9, 0xc2, 0xea, 0x59, 0x5b, 0x48, 0xb0, 0x91, 0xae, 0xe6, 0xc9, 0x03, 0xc9, 0x30, 0x51, 0x66, 0xd4, 0x0d, 0xad, 0xbd, 0x5f, 0x53, 0xcc, 0x6b, 0xb6, 0x90, 0x5a, 0x3b, 0x83, 0x0b, 0x43, 0x17, 0x31, 0xd6, 0xc3, 0x6e, 0x12, 0x3b, 0x79, 0xac, 0xc1, 0x89, 0x47, 0xd9, 0xe8, 0x63, 0x98, 0x45, 0xed, 0x6c, 0x5a, 0xf1, 0xa0, 0x27, 0xc5, 0x5b, 0xc3, 0x6f, 0xa6, 0xe0, 0x1c, 0x7d, 0xb3, 0xa2, 0x69, 0x34, 0x7b, 0xae, 0x1a, 0x8d, 0x45, 0x17, 0x9d, 0xeb, 0xfd, 0x21, 0xd8, 0xb9, 0xae, 0xb5, 0x80, 0xbb, 0x1e, 0xd2, 0x5c, 0xd7, 0x78, 0x13, 0xf9, 0xae, 0x4b, 0xea, 0xc7, 0x4a, 0x39, 0xbd, 0x55, 0xb3, 0xed, 0x66, 0x38, 0xf5, 0x09, 0x22, 0x41, 0x23, 0xe8, 0x37, 0xfb, 0x4b, 0xa1, 0xeb, 0xd6, 0xfe, 0x88, 0x31, 0xbf, 0x41, 0xc0, 0xee, 0xd2, 0x74, 0x02, 0x78, 0x53, 0xfa, 0x97, 0x43, 0x19, 0x85, 0x65, 0xff, 0x00, 0x9d, 0x71, 0x33, 0xe4, 0x1a, 0x7d, 0x8d, 0x53, 0x42, 0x56, 0x35, 0x6b, 0xe5, 0x80, 0x06, 0xc7, 0x57, 0xa7, 0xc4, 0xa9, 0xdb, 0xb6, 0x81, 0x1f, 0xeb, 0xd9, 0x69, 0x56, 0xc2, 0xd0, 0x00, 0xe5, 0x55, 0xc0, 0x12, 0xc2, 0xd7, 0x4e, 0xa2, 0x5a, 0x7c, 0x0a, 0xd0, 0x63, 0x9a, 0xd1, 0xaf, 0xd2, 0xe2, 0x3c, 0x12, 0x62, 0x66, 0xc6, 0x42, 0x23, 0x5a, 0x49, 0x8f, 0x10, 0xa2, 0xd2, 0x3e, 0x28, 0x9d, 0xc4, 0x88, 0x09, 0x29, 0x16, 0xc3, 0x3c, 0x24, 0x8d, 0xe6, 0x92, 0x72, 0x1f, 0xff, 0xd2, 0xf3, 0xbb, 0xb0, 0xfe, 0xcb, 0x99, 0xe9, 0xce, 0xf6, 0x88, 0x2d, 0x77, 0x91, 0x5b, 0x3d, 0x3d, 0xd0, 0xe6, 0x90, 0xa9, 0x65, 0x57, 0x38, 0x95, 0xdd, 0xcb, 0x9a, 0x7d, 0xce, 0xf2, 0x3f, 0x44, 0x23, 0x60, 0x58, 0x76, 0xe9, 0xca, 0x8c, 0xea, 0x1b, 0x31, 0x02, 0x32, 0x23, 0xea, 0xee, 0xb1, 0xcd, 0xb0, 0xc7, 0x87, 0x74, 0x7a, 0xeb, 0x70, 0x1a, 0x71, 0xe1, 0xfe, 0xe4, 0x1c, 0x1d, 0xae, 0xe5, 0x69, 0xd8, 0xfa, 0x99, 0x50, 0x0d, 0x1a, 0xf7, 0x2a, 0x3a, 0x0c, 0xf4, 0x1a, 0x8e, 0xc7, 0x27, 0x5d, 0xbf, 0x18, 0x41, 0xdc, 0xc2, 0xf0, 0x7f, 0x74, 0xf6, 0x3a, 0x22, 0x66, 0xdb, 0x68, 0xc6, 0x80, 0x48, 0x6b, 0x88, 0x06, 0x39, 0x0d, 0xee, 0xaa, 0x1f, 0xb3, 0xd5, 0x1b, 0x83, 0xd8, 0x3b, 0x38, 0x8f, 0x69, 0xfe, 0xdf, 0xd1, 0x4d, 0x29, 0xa1, 0x4c, 0x7a, 0xf4, 0xbf, 0xa7, 0x92, 0xcf, 0xa5, 0x20, 0x08, 0xf3, 0xf6, 0xff, 0x00, 0x15, 0xbb, 0xd1, 0x31, 0xd9, 0x5e, 0x3d, 0x75, 0x56, 0x36, 0x88, 0x00, 0x81, 0xe0, 0x16, 0x5e, 0x55, 0x74, 0x3f, 0x00, 0x9d, 0xe0, 0xcc, 0x69, 0xe7, 0x3a, 0x2d, 0xbe, 0x90, 0x00, 0xa9, 0xae, 0xef, 0x1f, 0x95, 0x4b, 0x0d, 0x9a, 0xdc, 0xc7, 0x45, 0xfe, 0xb1, 0x7d, 0x60, 0xa7, 0xa1, 0xe0, 0x1f, 0x4e, 0x1d, 0x99, 0x69, 0x02, 0x9a, 0xcf, 0x1f, 0xca, 0x7b, 0xbf, 0x90, 0xc5, 0xc2, 0xb3, 0xeb, 0x57, 0xd6, 0x03, 0x6b, 0xae, 0x39, 0xb6, 0x82, 0xe3, 0x31, 0xa1, 0x68, 0xf2, 0x6b, 0x5c, 0x12, 0xfa, 0xe1, 0x91, 0x66, 0x47, 0x5d, 0xb8, 0x3b, 0x4f, 0x44, 0x36, 0xb6, 0x8f, 0x28, 0xdd, 0xff, 0x00, 0x7e, 0x46, 0xab, 0x12, 0x2b, 0x65, 0x55, 0x32, 0xa7, 0x62, 0xb6, 0xbd, 0xf7, 0x64, 0x10, 0xdb, 0x03, 0x9f, 0x1b, 0x9e, 0xc7, 0xd9, 0xb8, 0x3b, 0x1f, 0x67, 0xf3, 0x6c, 0x52, 0x80, 0xd7, 0x7d, 0x0f, 0xea, 0x7f, 0x5d, 0x1d, 0x67, 0xa6, 0x0b, 0x1e, 0x47, 0xda, 0x69, 0x3b, 0x2e, 0x03, 0xc7, 0xf3, 0x5f, 0x1f, 0xf0, 0x8b, 0xa1, 0x02, 0x46, 0xba, 0x79, 0xaf, 0x32, 0xff, 0x00, 0x16, 0xad, 0xca, 0x1d, 0x57, 0x2a, 0xdc, 0x79, 0x18, 0x41, 0xb0, 0xf6, 0x9e, 0xe4, 0x9f, 0xd0, 0x8f, 0xeb, 0x31, 0xab, 0xd2, 0x83, 0xa4, 0xcb, 0x8c, 0xb8, 0xa0, 0x42, 0x12, 0x7b, 0x67, 0x9f, 0x2f, 0xf5, 0x09, 0x26, 0x96, 0xc4, 0xce, 0xa9, 0x20, 0xa7, 0xff, 0xd3, 0xf3, 0x2f, 0xb4, 0x5d, 0xe9, 0x0a, 0xb7, 0x9f, 0x4c, 0x19, 0xdb, 0x3a, 0x2d, 0x5e, 0x94, 0xfd, 0xc4, 0xb7, 0xc5, 0x62, 0xf9, 0x2b, 0xfd, 0x2e, 0xe3, 0x5d, 0xe0, 0x7c, 0x13, 0x48, 0xd1, 0x92, 0x12, 0xa9, 0x0b, 0x7a, 0xbc, 0x2d, 0xc2, 0x7f, 0x92, 0x60, 0xab, 0x4e, 0x79, 0x2e, 0x00, 0xf0, 0xaa, 0xe1, 0xda, 0x3d, 0x43, 0xfc, 0xad, 0x55, 0xbb, 0x80, 0x79, 0x81, 0xa0, 0xe6, 0x54, 0x32, 0x6d, 0x02, 0xbe, 0xf3, 0x61, 0x81, 0xa8, 0x44, 0x14, 0x03, 0x59, 0x0e, 0x1c, 0xf6, 0x1f, 0xdc, 0xb2, 0xec, 0xa3, 0x23, 0x77, 0xe8, 0x6e, 0x70, 0xf2, 0x25, 0x1f, 0x1f, 0x17, 0xa9, 0x6d, 0x71, 0x36, 0x97, 0x47, 0x00, 0xa4, 0x02, 0xe0, 0x2c, 0x7c, 0xc1, 0xab, 0xd5, 0x31, 0x85, 0x35, 0xd4, 0xe6, 0x13, 0x02, 0xd6, 0x4b, 0x67, 0x48, 0x2b, 0xa9, 0xe9, 0x2e, 0x02, 0xb6, 0x4f, 0x82, 0xe5, 0x7a, 0x95, 0x19, 0xc6, 0x87, 0x3d, 0xfb, 0xa2, 0xb8, 0x79, 0x1e, 0x4d, 0x3b, 0x96, 0xcf, 0x4f, 0xbd, 0xcd, 0xa2, 0xa2, 0x1f, 0xa0, 0x82, 0xd3, 0xfc, 0x97, 0x05, 0x24, 0x36, 0x6b, 0xf3, 0x31, 0xa2, 0x35, 0x79, 0xef, 0xad, 0xf8, 0xae, 0xaf, 0xaf, 0xd8, 0xf2, 0xd8, 0x6d, 0xed, 0x6b, 0xda, 0x7b, 0x18, 0x1b, 0x5d, 0xff, 0x00, 0x52, 0xb1, 0x6d, 0xf0, 0x81, 0x31, 0xca, 0xf4, 0x6e, 0xb1, 0x80, 0xce, 0xb1, 0x84, 0xc0, 0x21, 0xb7, 0xd6, 0x77, 0x31, 0xd1, 0x27, 0xc1, 0xcd, 0xfe, 0xd2, 0xe3, 0xec, 0xe8, 0x1d, 0x45, 0x96, 0xb0, 0x9a, 0xb7, 0x87, 0x3f, 0x68, 0x2d, 0xf7, 0x01, 0x1f, 0xbe, 0xd1, 0xf4, 0x7f, 0xb4, 0xa4, 0x0d, 0x77, 0xbb, 0xfa, 0x8f, 0x80, 0x3a, 0x7f, 0x43, 0xaa, 0xe2, 0xdf, 0xd2, 0x65, 0x7e, 0x95, 0xe4, 0x0f, 0x1f, 0xa1, 0xfe, 0x6b, 0x16, 0x9f, 0x52, 0xfa, 0xc1, 0xd3, 0xba, 0x6d, 0x26, 0xdc, 0xac, 0x86, 0xd4, 0xd9, 0x0d, 0x31, 0x2e, 0x74, 0x9e, 0xdb, 0x59, 0x2e, 0x55, 0xe8, 0xc9, 0xb2, 0x96, 0xd5, 0x4b, 0x9f, 0xb8, 0x6d, 0xda, 0x1c, 0x04, 0x09, 0x03, 0xfe, 0x8a, 0xc6, 0xfa, 0xd3, 0xf5, 0x6a, 0xbe, 0xbb, 0x5b, 0x2e, 0xc6, 0xb5, 0x94, 0xe6, 0xd5, 0x20, 0x97, 0x7d, 0x1b, 0x1b, 0xf9, 0xad, 0x7c, 0x7d, 0x17, 0xb7, 0xf3, 0x1e, 0x92, 0x1b, 0x7f, 0xf8, 0xe0, 0x7d, 0x59, 0xdd, 0xfd, 0x32, 0xd8, 0x8f, 0xa5, 0xe8, 0x3a, 0x12, 0x5c, 0x3f, 0xfc, 0xc4, 0xfa, 0xc3, 0xb3, 0x77, 0xa7, 0x56, 0xed, 0xdb, 0x76, 0x7a, 0x8d, 0xdd, 0x1f, 0xbf, 0xfd, 0x44, 0x92, 0x56, 0x8f, 0xff, 0xd4, 0xf2, 0xe8, 0x86, 0x17, 0x1e, 0xfa, 0x04, 0x56, 0x4b, 0x43, 0x6c, 0x6f, 0x2d, 0xe5, 0x46, 0x01, 0x64, 0x2b, 0x14, 0x32, 0x5b, 0xb4, 0xa0, 0x52, 0x1d, 0xde, 0x9b, 0x94, 0xdb, 0xab, 0x6b, 0x81, 0xf7, 0x05, 0xb0, 0xd7, 0x07, 0xb2, 0x27, 0x55, 0xc6, 0x57, 0x65, 0xd8, 0x76, 0x6e, 0x64, 0xed, 0xee, 0x16, 0xce, 0x27, 0x57, 0x63, 0xda, 0x0c, 0xc2, 0x8e, 0x51, 0x67, 0x84, 0xfa, 0x1d, 0xdd, 0x62, 0xc7, 0x07, 0xe9, 0xf7, 0xa3, 0xd6, 0x6c, 0x02, 0x41, 0x55, 0x31, 0xf3, 0x2b, 0xb3, 0xba, 0x2b, 0x2e, 0x68, 0x24, 0x1d, 0x47, 0x64, 0xca, 0xa6, 0x50, 0x41, 0x65, 0x90, 0x6c, 0xb1, 0xa5, 0xae, 0x33, 0x23, 0x51, 0xe4, 0xab, 0x7d, 0x5d, 0xcb, 0xb6, 0xcc, 0x37, 0xd0, 0x40, 0x73, 0x71, 0xde, 0x58, 0x09, 0xe7, 0x6f, 0x2c, 0x44, 0xc9, 0xc9, 0xae, 0xba, 0x9d, 0x63, 0x88, 0x01, 0xa0, 0x95, 0x9d, 0xf5, 0x3f, 0x2a, 0xe6, 0x67, 0xdb, 0x50, 0x83, 0x55, 0xad, 0x36, 0x3e, 0x78, 0x10, 0x74, 0x77, 0xfd, 0x2d, 0xaa, 0x4c, 0x7d, 0x58, 0x73, 0x91, 0xa0, 0x0f, 0x51, 0x45, 0xb7, 0x33, 0xdd, 0x58, 0x69, 0x1d, 0xd8, 0x0c, 0x9f, 0x96, 0x88, 0x19, 0x99, 0x19, 0xac, 0xcf, 0xa3, 0xd2, 0xad, 0xb5, 0xdb, 0x76, 0x8f, 0xad, 0xc4, 0xea, 0xcf, 0xdf, 0x7e, 0xdf, 0xdd, 0xfc, 0xd5, 0xa3, 0x5e, 0x43, 0x2b, 0x6b, 0xb2, 0xad, 0x3b, 0x6a, 0xa4, 0x13, 0xa7, 0x04, 0xac, 0x7a, 0x6f, 0xb3, 0x23, 0x26, 0xcc, 0xfb, 0xb4, 0x75, 0x8e, 0x01, 0x83, 0xf7, 0x58, 0x3e, 0x8b, 0x53, 0xa7, 0x2a, 0x1a, 0x31, 0x42, 0x36, 0x5d, 0x4c, 0x9a, 0xf2, 0xdc, 0xc6, 0xfe, 0x98, 0xb4, 0x34, 0xcb, 0x48, 0x0a, 0x8f, 0xdb, 0xb2, 0xeb, 0x76, 0xd6, 0x07, 0x5c, 0x59, 0xc9, 0x64, 0x8f, 0x93, 0xa7, 0x73, 0x16, 0x83, 0xaf, 0x0e, 0xa4, 0x33, 0xef, 0x50, 0xc5, 0x0c, 0xda, 0x59, 0x10, 0x06, 0x8a, 0x2e, 0x29, 0x0e, 0xac, 0xc2, 0x31, 0x3d, 0x36, 0x69, 0x7e, 0xd6, 0xcc, 0xf5, 0x3d, 0x6f, 0xb3, 0xeb, 0x1b, 0x76, 0xef, 0x3b, 0xa3, 0xfa, 0xc9, 0x2b, 0x5f, 0x66, 0x6f, 0xa9, 0x1e, 0x73, 0xf2, 0x49, 0x2e, 0x39, 0xf7, 0x4f, 0xb7, 0x8d, 0xff, 0xd5, 0xf3, 0x26, 0xfe, 0x0a, 0xc5, 0x1b, 0xa7, 0xcb, 0xb2, 0xcf, 0x49, 0x03, 0xb2, 0x46, 0xee, 0xd9, 0xd9, 0xb3, 0xf4, 0x9f, 0x25, 0x4a, 0xdf, 0x4b, 0x77, 0xe8, 0x27, 0xd4, 0xef, 0x1c, 0x2a, 0x29, 0x26, 0xc5, 0x7c, 0x9d, 0x6c, 0x7f, 0xb7, 0x6e, 0x1b, 0x26, 0x7f, 0x05, 0xa3, 0xfe, 0x53, 0x8d, 0x62, 0x57, 0x30, 0x92, 0x12, 0xfa, 0x2f, 0x86, 0xdf, 0xa4, 0xec, 0x67, 0xfe, 0xd0, 0xf4, 0xff, 0x00, 0x4d, 0xfc, 0xdf, 0x78, 0xe1, 0x68, 0x7d, 0x54, 0x99, 0xbf, 0x6f, 0xf3, 0xbe, 0xdf, 0x8e, 0xdd, 0x7f, 0xef, 0xeb, 0x97, 0x49, 0x3e, 0x3b, 0x7f, 0x06, 0x2c, 0x9f, 0x37, 0x5f, 0xf0, 0x9f, 0x4c, 0xeb, 0x7b, 0xbf, 0x67, 0x55, 0xe8, 0xff, 0x00, 0x31, 0xbc, 0x7a, 0x9e, 0x31, 0xdb, 0xfe, 0x92, 0xae, 0x37, 0x7a, 0x4d, 0xdb, 0xe2, 0x17, 0x9d, 0xa4, 0xa3, 0xc9, 0xba, 0xfc, 0x7b, 0x7d, 0x5f, 0x52, 0xa7, 0x7e, 0xd1, 0x28, 0xf8, 0xf3, 0xb0, 0xc7, 0x32, 0xbc, 0x99, 0x24, 0xc5, 0xe3, 0xab, 0xeb, 0x1f, 0xa4, 0xf5, 0xfc, 0xe1, 0x25, 0xe4, 0xe9, 0x24, 0x97, 0xff, 0xd9, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x41, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x00, 0x00, 0x13, 0x00, 0x41, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x20, 0x00, 0x37, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xff, 0xe1, 0x15, 0x67, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x00, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x27, 0xef, 0xbb, 0xbf, 0x27, 0x20, 0x69, 0x64, 0x3d, 0x27, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x27, 0x3f, 0x3e, 0x0a, 0x3c, 0x3f, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2d, 0x78, 0x61, 0x70, 0x2d, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x20, 0x65, 0x73, 0x63, 0x3d, 0x22, 0x43, 0x52, 0x22, 0x3f, 0x3e, 0x0a, 0x3c, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x27, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x27, 0x20, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x74, 0x6b, 0x3d, 0x27, 0x58, 0x4d, 0x50, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x6b, 0x69, 0x74, 0x20, 0x32, 0x2e, 0x38, 0x2e, 0x32, 0x2d, 0x33, 0x33, 0x2c, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x31, 0x2e, 0x35, 0x27, 0x3e, 0x0a, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x27, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x69, 0x58, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x58, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x70, 0x64, 0x66, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x64, 0x66, 0x2f, 0x31, 0x2e, 0x33, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x70, 0x64, 0x66, 0x3a, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x43, 0x61, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x61, 0x70, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x78, 0x61, 0x70, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x64, 0x6f, 0x63, 0x69, 0x64, 0x3a, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x36, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x3c, 0x2f, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x64, 0x63, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x75, 0x72, 0x6c, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x63, 0x2f, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x31, 0x2e, 0x31, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x64, 0x63, 0x3a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x20, 0x78, 0x6d, 0x6c, 0x3a, 0x6c, 0x61, 0x6e, 0x67, 0x3d, 0x27, 0x78, 0x2d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x27, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x64, 0x63, 0x3a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x0a, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x27, 0x77, 0x27, 0x3f, 0x3e, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x40, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x06, 0x04, 0x03, 0x04, 0x06, 0x07, 0x05, 0x04, 0x04, 0x05, 0x07, 0x08, 0x06, 0x06, 0x07, 0x06, 0x06, 0x08, 0x0a, 0x08, 0x09, 0x09, 0x09, 0x09, 0x08, 0x0a, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x04, 0x05, 0x05, 0x08, 0x07, 0x08, 0x0f, 0x0a, 0x0a, 0x0f, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x0d, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x00, 0x07, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x05, 0x03, 0x02, 0x06, 0x01, 0x00, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x02, 0x02, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x02, 0x06, 0x07, 0x03, 0x04, 0x02, 0x06, 0x02, 0x73, 0x01, 0x02, 0x03, 0x11, 0x04, 0x00, 0x05, 0x21, 0x12, 0x31, 0x41, 0x51, 0x06, 0x13, 0x61, 0x22, 0x71, 0x81, 0x14, 0x32, 0x91, 0xa1, 0x07, 0x15, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xe1, 0x33, 0x16, 0x62, 0xf0, 0x24, 0x72, 0x82, 0xf1, 0x25, 0x43, 0x34, 0x53, 0x92, 0xa2, 0xb2, 0x63, 0x73, 0xc2, 0x35, 0x44, 0x27, 0x93, 0xa3, 0xb3, 0x36, 0x17, 0x54, 0x64, 0x74, 0xc3, 0xd2, 0xe2, 0x08, 0x26, 0x83, 0x09, 0x0a, 0x18, 0x19, 0x84, 0x94, 0x45, 0x46, 0xa4, 0xb4, 0x56, 0xd3, 0x55, 0x28, 0x1a, 0xf2, 0xe3, 0xf3, 0xc4, 0xd4, 0xe4, 0xf4, 0x65, 0x75, 0x85, 0x95, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8, 0xd8, 0xe8, 0xf8, 0x29, 0x39, 0x49, 0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, 0xb9, 0xc9, 0xd9, 0xe9, 0xf9, 0x2a, 0x3a, 0x4a, 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, 0xba, 0xca, 0xda, 0xea, 0xfa, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x03, 0x05, 0x05, 0x04, 0x05, 0x06, 0x04, 0x08, 0x03, 0x03, 0x6d, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x41, 0x05, 0x51, 0x13, 0x61, 0x22, 0x06, 0x71, 0x81, 0x91, 0x32, 0xa1, 0xb1, 0xf0, 0x14, 0xc1, 0xd1, 0xe1, 0x23, 0x42, 0x15, 0x52, 0x62, 0x72, 0xf1, 0x33, 0x24, 0x34, 0x43, 0x82, 0x16, 0x92, 0x53, 0x25, 0xa2, 0x63, 0xb2, 0xc2, 0x07, 0x73, 0xd2, 0x35, 0xe2, 0x44, 0x83, 0x17, 0x54, 0x93, 0x08, 0x09, 0x0a, 0x18, 0x19, 0x26, 0x36, 0x45, 0x1a, 0x27, 0x64, 0x74, 0x55, 0x37, 0xf2, 0xa3, 0xb3, 0xc3, 0x28, 0x29, 0xd3, 0xe3, 0xf3, 0x84, 0x94, 0xa4, 0xb4, 0xc4, 0xd4, 0xe4, 0xf4, 0x65, 0x75, 0x85, 0x95, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x46, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8, 0xd8, 0xe8, 0xf8, 0x39, 0x49, 0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, 0xb9, 0xc9, 0xd9, 0xe9, 0xf9, 0x2a, 0x3a, 0x4a, 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, 0xba, 0xca, 0xda, 0xea, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf0, 0x67, 0xa6, 0x5c, 0x0f, 0x01, 0xd4, 0x7e, 0x18, 0x12, 0x98, 0xe9, 0xd6, 0x2d, 0x34, 0x6d, 0x70, 0xdf, 0xdc, 0xa1, 0xe3, 0xec, 0x5b, 0xfb, 0x32, 0x24, 0xb2, 0x01, 0x1f, 0x15, 0xa4, 0x52, 0x4a, 0x82, 0x31, 0xf1, 0xfe, 0xd1, 0x3d, 0x14, 0x64, 0x49, 0x64, 0x22, 0x98, 0xcf, 0xa5, 0x46, 0x6c, 0x16, 0x55, 0x71, 0x56, 0x62, 0x28, 0x07, 0xc5, 0x45, 0x15, 0xa0, 0xc8, 0x89, 0x33, 0xe1, 0x63, 0xd2, 0xd8, 0x34, 0x44, 0x17, 0xa0, 0x2c, 0x4d, 0x16, 0xbb, 0xed, 0xdc, 0xf8, 0x64, 0xc1, 0x6b, 0x31, 0x42, 0x18, 0x8e, 0xc7, 0xb5, 0x2a, 0x7d, 0xb2, 0x56, 0xc5, 0x61, 0x8c, 0xf2, 0xa0, 0x1b, 0x1e, 0x83, 0x0d, 0xa1, 0x63, 0x50, 0x1f, 0x97, 0x7c, 0x2a, 0xa9, 0x1a, 0x9a, 0x86, 0x4f, 0xb4, 0xb4, 0x38, 0x0a, 0xa6, 0x0b, 0xb8, 0x0c, 0x05, 0x14, 0xf8, 0x76, 0x3e, 0x19, 0x14, 0xb6, 0x78, 0xf8, 0x8c, 0x2a, 0xd5, 0x01, 0xdc, 0x6f, 0x8a, 0x1a, 0xe3, 0x8d, 0xab, 0xff, 0xd0, 0xf0, 0xec, 0xe9, 0x15, 0xb5, 0xb9, 0x5a, 0x7c, 0x4c, 0xa2, 0x9e, 0x24, 0xf5, 0xca, 0xc6, 0xe5, 0x99, 0xd9, 0x34, 0x99, 0x04, 0x3a, 0x7d, 0xb5, 0xba, 0xd5, 0x51, 0x63, 0x0e, 0xc7, 0xc5, 0x9b, 0x73, 0xf8, 0xe4, 0x6f, 0x76, 0xca, 0xd9, 0xda, 0x54, 0x6d, 0x72, 0x2e, 0x1a, 0x57, 0x11, 0x44, 0x40, 0x0d, 0x27, 0x7a, 0x0f, 0xd9, 0x5f, 0x12, 0x69, 0x4c, 0x84, 0xcd, 0x36, 0xe3, 0x85, 0xb2, 0xcd, 0x2f, 0x4a, 0x8b, 0x58, 0x36, 0xf6, 0x76, 0xa8, 0x64, 0x64, 0x3c, 0xa4, 0x93, 0xaa, 0x25, 0x3c, 0x49, 0xda, 0xa4, 0xe5, 0x26, 0x54, 0xe4, 0x8c, 0x7c, 0x5c, 0x93, 0x4d, 0x67, 0xc9, 0x3a, 0x6e, 0x9f, 0x13, 0xb4, 0xce, 0xf7, 0x3a, 0x9b, 0xad, 0x52, 0xd6, 0x2a, 0xd1, 0x49, 0xee, 0xc7, 0xf8, 0x64, 0x46, 0x42, 0x4e, 0xcd, 0x92, 0xc2, 0x00, 0xdd, 0x8a, 0x47, 0xe5, 0x69, 0x6e, 0xd4, 0xa4, 0x08, 0x16, 0x83, 0x9c, 0x8c, 0xdd, 0x95, 0x6b, 0xb9, 0xf6, 0xef, 0x97, 0x78, 0x94, 0xe3, 0x78, 0x04, 0xa4, 0xf3, 0xe8, 0xee, 0x64, 0xe1, 0x12, 0x10, 0x05, 0x6a, 0xc7, 0xc0, 0x6f, 0x53, 0xf3, 0xc9, 0x89, 0xb4, 0x9c, 0x4e, 0xb4, 0xf2, 0xd3, 0xde, 0x7a, 0xd2, 0x19, 0x16, 0x38, 0x61, 0x5d, 0xd9, 0x88, 0x05, 0x9c, 0xf4, 0x0a, 0x0f, 0x5f, 0x73, 0x84, 0xe4, 0xa4, 0xc7, 0x0d, 0xa5, 0xf1, 0x59, 0xba, 0x5c, 0x08, 0x98, 0x6f, 0xc8, 0x20, 0xfa, 0x4e, 0x4e, 0xf6, 0x69, 0xe1, 0xa2, 0x89, 0xfd, 0x1f, 0x77, 0x2c, 0xe6, 0xce, 0xd6, 0x17, 0x9a, 0x69, 0xdb, 0xd3, 0x86, 0x18, 0xc1, 0x67, 0x77, 0x26, 0x80, 0x28, 0x1b, 0x93, 0x88, 0x41, 0x0f, 0x40, 0xb0, 0xfc, 0x87, 0xf3, 0x43, 0x98, 0xd7, 0x58, 0x96, 0xdb, 0x4d, 0x91, 0x88, 0xe5, 0x6c, 0x58, 0xdc, 0x5c, 0x2a, 0xf7, 0x2c, 0xb1, 0xfc, 0x20, 0x8f, 0x02, 0xd9, 0x65, 0x06, 0xbe, 0x26, 0x6f, 0xa2, 0x7f, 0xce, 0x3d, 0x69, 0x26, 0xdd, 0x13, 0x52, 0xbf, 0xbd, 0x92, 0x62, 0x59, 0x4c, 0x90, 0xac, 0x50, 0x45, 0x5e, 0xbb, 0x09, 0x03, 0x12, 0x29, 0x84, 0x00, 0xc4, 0xc9, 0x11, 0xff, 0x00, 0x42, 0xe7, 0xa7, 0x7a, 0xd4, 0xfd, 0x21, 0x79, 0xe9, 0x78, 0x71, 0x8b, 0x95, 0x39, 0x75, 0xaf, 0x4e, 0x98, 0x78, 0x42, 0x38, 0xdf, 0xff, 0xd1, 0xf0, 0xe6, 0xa0, 0x58, 0xc8, 0x84, 0x9a, 0xaa, 0x30, 0x55, 0xf9, 0x0a, 0x6f, 0x90, 0x0c, 0xca, 0x72, 0x48, 0xb8, 0x1e, 0x89, 0xa7, 0x23, 0x17, 0x24, 0xff, 0x00, 0x61, 0xb6, 0x54, 0x76, 0x6e, 0x1b, 0xa7, 0xbe, 0x50, 0xf2, 0xc1, 0xd7, 0x4c, 0x52, 0x5e, 0x33, 0x5b, 0xe9, 0x10, 0xf4, 0x54, 0x3c, 0x5e, 0x77, 0xee, 0x49, 0xec, 0x2b, 0xb6, 0x63, 0xe4, 0xc9, 0xc3, 0xef, 0x73, 0xf0, 0xe1, 0x32, 0x1b, 0xf2, 0x7a, 0x05, 0xce, 0xad, 0x65, 0xa1, 0x98, 0xb4, 0x0f, 0x2a, 0x5b, 0x23, 0xeb, 0x12, 0x00, 0x88, 0xb0, 0xa8, 0x66, 0x46, 0x3d, 0xea, 0x7b, 0xfb, 0x9e, 0x99, 0x89, 0xbc, 0x8d, 0x97, 0x3a, 0x34, 0x05, 0x32, 0x5d, 0x1f, 0xc9, 0x1a, 0x8c, 0x36, 0x8c, 0x6f, 0x66, 0xfa, 0xc6, 0xb7, 0x7d, 0xf0, 0x94, 0x04, 0xf0, 0x88, 0xc9, 0xd5, 0x9d, 0x8d, 0x4b, 0x11, 0xd4, 0x9f, 0xbb, 0x25, 0xc5, 0xdc, 0xa2, 0x03, 0x99, 0x4b, 0xbc, 0xf3, 0x0d, 0x97, 0x96, 0x74, 0xe5, 0xf2, 0xb6, 0x80, 0x95, 0xbd, 0x99, 0x15, 0xf5, 0x4b, 0xd2, 0x37, 0x58, 0x46, 0xd4, 0x27, 0xc5, 0xce, 0xc1, 0x7c, 0x30, 0x8e, 0x68, 0x94, 0x7b, 0x9e, 0x6d, 0xe6, 0x7b, 0x9b, 0x5d, 0x3a, 0xd8, 0xdb, 0x32, 0xfa, 0x77, 0x65, 0x15, 0xe4, 0x57, 0xa7, 0x21, 0x55, 0x04, 0x57, 0xef, 0xd8, 0x66, 0x56, 0x38, 0x19, 0x1b, 0xe8, 0xe0, 0x67, 0x98, 0xc7, 0x1a, 0x1c, 0xde, 0x71, 0x71, 0x79, 0x2c, 0xf2, 0xfa, 0x8c, 0x48, 0xec, 0xb5, 0x24, 0x9a, 0x0c, 0xce, 0x75, 0x29, 0xae, 0x8c, 0x67, 0xd4, 0xb5, 0x0b, 0x4b, 0x04, 0x05, 0xef, 0x2e, 0x66, 0x8e, 0x18, 0x08, 0x15, 0xdd, 0x8f, 0x11, 0xb0, 0xeb, 0x4c, 0x04, 0x5b, 0x21, 0x2a, 0x7d, 0x41, 0xe4, 0x4f, 0xcb, 0xcb, 0x5d, 0x12, 0x45, 0xb8, 0xb7, 0x53, 0x71, 0xaa, 0x9f, 0x86, 0x5b, 0xd6, 0x50, 0x4a, 0xed, 0xba, 0x46, 0x77, 0x00, 0x13, 0xd4, 0x8c, 0x85, 0xd3, 0x12, 0x6d, 0xeb, 0x1a, 0x67, 0x95, 0xd9, 0x39, 0x39, 0x50, 0xac, 0xff, 0x00, 0x6f, 0xc4, 0xff, 0x00, 0x1c, 0x81, 0x92, 0xb2, 0x6b, 0x6d, 0x02, 0xdd, 0xbd, 0x36, 0x92, 0x36, 0x2d, 0x1f, 0xc0, 0x2a, 0x0b, 0x28, 0x1b, 0x91, 0x41, 0xf4, 0x9c, 0xb6, 0x25, 0x81, 0x46, 0xfe, 0x81, 0xb5, 0xad, 0x3d, 0xba, 0x57, 0xb7, 0xf9, 0xf6, 0xc9, 0xb0, 0x7f, 0xff, 0xd2, 0xf0, 0xe2, 0x86, 0x95, 0xc4, 0x67, 0x7e, 0x3f, 0x11, 0xf7, 0xa8, 0x19, 0x06, 0x69, 0x8d, 0xca, 0xca, 0x24, 0x8f, 0xd3, 0x52, 0x24, 0x89, 0x47, 0x25, 0x1f, 0xcb, 0x20, 0xf8, 0xb2, 0xb2, 0x76, 0x6e, 0x88, 0x36, 0xf6, 0x6f, 0x2a, 0xc1, 0x6e, 0xfa, 0x45, 0xad, 0xbc, 0x3f, 0x0b, 0x46, 0x81, 0x4d, 0x46, 0xea, 0x7a, 0x9a, 0x83, 0x9a, 0xa9, 0xdd, 0xbb, 0xec, 0x7b, 0x06, 0x5b, 0xe5, 0xcf, 0x2e, 0x69, 0xfa, 0x5c, 0xcd, 0x7b, 0x14, 0x5e, 0xa5, 0xee, 0xf5, 0xb8, 0x7d, 0xdd, 0x99, 0xba, 0xef, 0x91, 0x16, 0x5b, 0x36, 0xb6, 0x65, 0x0d, 0xac, 0xb2, 0x5b, 0xed, 0x34, 0x81, 0x7a, 0xbb, 0x46, 0x40, 0x6a, 0x9e, 0xb4, 0x39, 0x31, 0x13, 0x49, 0xda, 0xd2, 0x9b, 0xed, 0x1e, 0xc4, 0x24, 0xb3, 0x35, 0xb2, 0x88, 0x60, 0x06, 0xe6, 0x56, 0x98, 0x96, 0x79, 0x1e, 0x31, 0x51, 0xc9, 0x8f, 0xcb, 0x00, 0xe6, 0xb3, 0xe4, 0xf9, 0x2b, 0xcc, 0x7a, 0x94, 0xda, 0x96, 0xa9, 0x71, 0x77, 0x70, 0x79, 0xcd, 0x33, 0x97, 0x76, 0x3f, 0xcc, 0xc6, 0xa6, 0x9f, 0x2e, 0x99, 0xb9, 0xc6, 0x2a, 0x21, 0xe6, 0x73, 0xca, 0xe6, 0x4a, 0x51, 0x1a, 0x99, 0x1c, 0x28, 0x04, 0x93, 0xd0, 0x0e, 0xa4, 0xe4, 0xda, 0x5f, 0x50, 0xfe, 0x4a, 0xfe, 0x48, 0xb5, 0xb2, 0xc1, 0xe6, 0x1f, 0x31, 0x7e, 0xef, 0x52, 0x91, 0x43, 0xc3, 0x6e, 0x77, 0xf4, 0x22, 0x6d, 0xbf, 0xe4, 0x63, 0x0e, 0xbf, 0xca, 0x36, 0xeb, 0x5c, 0x84, 0xa5, 0x48, 0x7d, 0x3b, 0x61, 0xa1, 0xdb, 0x5b, 0x2c, 0x71, 0xda, 0x45, 0xc4, 0x28, 0x00, 0x81, 0xdb, 0x31, 0xc9, 0xb4, 0xb2, 0x3b, 0x5d, 0x27, 0xa5, 0x05, 0x1b, 0xc7, 0xdb, 0x10, 0xa9, 0xbd, 0xa6, 0x93, 0x0c, 0x75, 0xe4, 0x39, 0x35, 0x41, 0x3d, 0xc5, 0x06, 0xdb, 0x8e, 0xfd, 0x46, 0x5b, 0x1d, 0x98, 0x95, 0x4f, 0x46, 0xdb, 0xd5, 0xfb, 0x29, 0x5e, 0x9d, 0x0d, 0x32, 0xeb, 0x61, 0x4f, 0xff, 0xd3, 0xf1, 0x46, 0x9a, 0x16, 0x1b, 0x91, 0x71, 0x28, 0xac, 0x4a, 0x14, 0x30, 0x3e, 0x19, 0x54, 0xb9, 0x36, 0xc7, 0x9b, 0x2d, 0xd1, 0x6c, 0x45, 0xe3, 0xdc, 0xde, 0xc8, 0x95, 0x5b, 0x87, 0xf8, 0x41, 0x1d, 0x10, 0x54, 0x01, 0x98, 0x79, 0x25, 0xd1, 0xda, 0xe9, 0xe1, 0xb5, 0x9e, 0xac, 0xeb, 0x42, 0xba, 0x8e, 0xdf, 0x8c, 0x31, 0x21, 0x70, 0xb4, 0x5d, 0xbe, 0xc5, 0x7c, 0x2b, 0xed, 0xe1, 0x94, 0x18, 0xb9, 0x51, 0x3d, 0x03, 0x2c, 0x13, 0x6b, 0xf1, 0x42, 0x6e, 0xe2, 0xb7, 0x12, 0xa0, 0xdd, 0x50, 0x9f, 0x4f, 0x6f, 0xa7, 0x6f, 0xc7, 0x03, 0x61, 0xa0, 0x83, 0xb5, 0xf3, 0x97, 0x98, 0x20, 0x9c, 0x44, 0xea, 0xd0, 0xad, 0x48, 0x64, 0x90, 0x21, 0xd8, 0x9f, 0xa7, 0xa6, 0x44, 0xca, 0x99, 0xc6, 0x36, 0xcb, 0x74, 0x5d, 0x7e, 0x5b, 0xfe, 0x31, 0x6a, 0x31, 0xf3, 0x8c, 0xd0, 0xad, 0x40, 0xa3, 0x1f, 0x7c, 0x44, 0xd6, 0x51, 0xd9, 0xe0, 0x5f, 0x9a, 0x7e, 0x41, 0x9f, 0x40, 0xf3, 0x14, 0xba, 0x85, 0xba, 0x34, 0xba, 0x2d, 0xfb, 0x34, 0xd0, 0xcf, 0x4f, 0xb0, 0xce, 0x6a, 0x51, 0xe9, 0xb0, 0x20, 0xf4, 0xf1, 0x19, 0xb2, 0xc3, 0x90, 0x11, 0x4e, 0x97, 0x55, 0x80, 0x83, 0xc4, 0x17, 0x7e, 0x4c, 0x79, 0x19, 0xfc, 0xd1, 0xe7, 0x78, 0x4b, 0x91, 0x1d, 0xae, 0x92, 0xa6, 0xf6, 0x46, 0x75, 0xe4, 0xad, 0x22, 0x1f, 0xdd, 0xa1, 0x07, 0xb3, 0x1e, 0xfe, 0xd9, 0x92, 0xeb, 0x4b, 0xed, 0xfd, 0x0a, 0xc2, 0x63, 0x27, 0xa4, 0x88, 0x17, 0x60, 0x49, 0x35, 0xdc, 0x8e, 0xa5, 0x7d, 0xab, 0xd3, 0x28, 0x90, 0x50, 0xcd, 0xed, 0x2d, 0xda, 0x15, 0x55, 0x51, 0xf1, 0x1a, 0x0a, 0xf7, 0x39, 0x5d, 0xaa, 0x77, 0x6f, 0x01, 0x8e, 0xa7, 0x7d, 0xfa, 0xff, 0x00, 0x66, 0x10, 0xa8, 0xb8, 0x63, 0x76, 0x90, 0xa8, 0x20, 0x06, 0x56, 0xdb, 0x61, 0xda, 0xbd, 0x4f, 0xcb, 0x24, 0x15, 0x0f, 0xf5, 0x66, 0xe5, 0x5f, 0x4c, 0x53, 0xc3, 0xb7, 0xce, 0x99, 0x6b, 0x17, 0xff, 0xd4, 0xf0, 0xec, 0x57, 0x6f, 0x32, 0xa5, 0xa4, 0x43, 0x76, 0x75, 0xa9, 0xf1, 0x03, 0xfa, 0x64, 0x08, 0x6c, 0x8e, 0xfb, 0x3d, 0x7f, 0xcb, 0x16, 0x2b, 0x3d, 0xbc, 0x16, 0xa3, 0x66, 0x6d, 0x98, 0xfb, 0x1e, 0xb9, 0xac, 0xc8, 0x77, 0xb7, 0x7d, 0x01, 0xb3, 0x37, 0xb8, 0xd3, 0x46, 0x95, 0x68, 0x86, 0xd2, 0x2e, 0x4e, 0xab, 0xf0, 0x23, 0x11, 0x4e, 0x5f, 0xcd, 0x98, 0xe7, 0x25, 0x96, 0x71, 0x83, 0x0f, 0xd6, 0x3c, 0xb9, 0xe7, 0x0d, 0x7c, 0x41, 0x22, 0x5e, 0xb3, 0x20, 0x0c, 0x65, 0x80, 0xc8, 0x63, 0x8e, 0xbb, 0x95, 0xa5, 0x07, 0xeb, 0xcc, 0xac, 0x73, 0x83, 0x4e, 0x5c, 0x59, 0x09, 0xd8, 0xec, 0xc8, 0x57, 0x41, 0xd3, 0x4e, 0x95, 0xa5, 0x5b, 0x4b, 0x6a, 0xcb, 0xab, 0x43, 0x10, 0x4b, 0xeb, 0x85, 0xa2, 0x2c, 0x8e, 0x3f, 0x68, 0x54, 0xf5, 0x00, 0xd3, 0x97, 0x7a, 0x65, 0x79, 0xa6, 0x24, 0x76, 0x6f, 0xd3, 0x62, 0x96, 0x30, 0x78, 0xcb, 0x21, 0xf2, 0xf4, 0x22, 0xce, 0x54, 0x8e, 0x46, 0x26, 0x10, 0x7e, 0x0a, 0xf5, 0xd8, 0xf5, 0x1f, 0x31, 0x98, 0x83, 0x73, 0xb3, 0x91, 0xcd, 0x67, 0xe6, 0x7d, 0xe8, 0x16, 0x69, 0x6f, 0x10, 0x1f, 0x54, 0x9a, 0x37, 0xf5, 0x41, 0x5e, 0x7f, 0x0a, 0x29, 0x62, 0x02, 0xf8, 0x9c, 0xc8, 0x8c, 0x77, 0x6a, 0x99, 0xa0, 0x89, 0xff, 0x00, 0x9c, 0x74, 0xd2, 0xed, 0xed, 0xfc, 0xbb, 0x7b, 0xaa, 0x9a, 0x7d, 0x62, 0xfe, 0x46, 0x2d, 0xfe, 0x4c, 0x51, 0x31, 0x11, 0xa9, 0xf6, 0xef, 0x9b, 0x30, 0x5e, 0x7b, 0x38, 0xdd, 0xf4, 0x7f, 0x95, 0x94, 0xbc, 0x12, 0x43, 0x30, 0x6a, 0xb2, 0xf3, 0x86, 0x40, 0x3e, 0xcb, 0xd7, 0x6a, 0xd7, 0xb1, 0xe9, 0x8f, 0x37, 0x19, 0x97, 0x41, 0x2c, 0x71, 0x20, 0xf5, 0x36, 0x9c, 0x55, 0x78, 0x1d, 0x8a, 0x91, 0xd7, 0x11, 0x14, 0x5a, 0x3e, 0x19, 0x03, 0x10, 0x6b, 0xca, 0xbd, 0x86, 0xf8, 0x9d, 0x95, 0x18, 0x36, 0x65, 0x2e, 0xbc, 0x54, 0x1f, 0xa2, 0x99, 0x00, 0x59, 0x2a, 0x6f, 0x5e, 0x55, 0x15, 0xe9, 0x5f, 0xc3, 0x2f, 0xb6, 0x14, 0xff, 0x00, 0xff, 0xd5, 0xf1, 0x95, 0xfe, 0x80, 0x74, 0x0d, 0x7c, 0xd9, 0x89, 0x3d, 0x78, 0x57, 0x8b, 0xc5, 0x28, 0xe8, 0x55, 0xf7, 0x1f, 0x48, 0xca, 0x38, 0xb8, 0x83, 0x9f, 0x93, 0x07, 0x85, 0x3a, 0x7a, 0x6f, 0x95, 0x66, 0x2b, 0x2c, 0x4c, 0x0d, 0x14, 0x00, 0x3e, 0x9c, 0xc3, 0x98, 0x76, 0xb8, 0x45, 0xbd, 0x02, 0xde, 0x48, 0xee, 0xdc, 0xa0, 0x15, 0xe2, 0x2b, 0xc8, 0x8a, 0x8a, 0xfd, 0x3b, 0x66, 0x3f, 0x00, 0x73, 0x84, 0x2d, 0x36, 0xb5, 0xb5, 0x9e, 0x35, 0x1c, 0x29, 0xc4, 0xfe, 0xc8, 0x04, 0x7f, 0xc4, 0x69, 0x91, 0xe1, 0x67, 0x2c, 0x4a, 0xd2, 0xe9, 0x4e, 0xe3, 0xd4, 0xf4, 0x81, 0x5a, 0x12, 0xc5, 0x41, 0x3f, 0x79, 0x38, 0x9b, 0x60, 0x20, 0x07, 0x34, 0xb0, 0xc9, 0x03, 0x5c, 0x23, 0x03, 0x53, 0x13, 0x56, 0x88, 0xdf, 0x09, 0xda, 0x9b, 0xd3, 0xb6, 0x52, 0x0e, 0xec, 0xe4, 0x29, 0x24, 0xfc, 0xd0, 0xe7, 0x75, 0xe5, 0x57, 0x6b, 0x61, 0xfb, 0xf0, 0xca, 0xaa, 0x57, 0xa8, 0xe6, 0x78, 0x1a, 0x7d, 0xf9, 0x95, 0x8a, 0x5e, 0xa0, 0xe3, 0x67, 0x8f, 0xa0, 0xbd, 0x5b, 0xf2, 0xdf, 0x4a, 0x82, 0xcb, 0x4a, 0xb3, 0xb0, 0xb4, 0x41, 0x0a, 0x70, 0x48, 0xd9, 0x57, 0x60, 0x51, 0x3a, 0x8f, 0xbc, 0xe6, 0x7b, 0xcb, 0xe4, 0x3b, 0xa7, 0x3f, 0x9b, 0x9f, 0x9a, 0xba, 0x77, 0xe5, 0x5f, 0x95, 0x9c, 0x59, 0x94, 0x9f, 0xcd, 0x37, 0x8c, 0xa9, 0xa6, 0xd9, 0x39, 0xaa, 0xd0, 0x7d, 0xa9, 0x1c, 0x03, 0x5e, 0x09, 0xff, 0x00, 0x0c, 0x76, 0xcb, 0x62, 0x2d, 0xa5, 0xf2, 0x85, 0xbf, 0xe7, 0x87, 0xe6, 0xa3, 0x5e, 0x4d, 0xa8, 0xc9, 0xe6, 0x8b, 0xd5, 0x69, 0x5c, 0xb0, 0x4a, 0xab, 0xc4, 0xb5, 0x35, 0x0a, 0xaa, 0xea, 0x40, 0x03, 0xa0, 0xf6, 0xcb, 0x40, 0x4d, 0x3e, 0xdb, 0xff, 0x00, 0x9c, 0x7f, 0xfc, 0xce, 0x4f, 0xcc, 0xbf, 0x26, 0x25, 0xe5, 0xd3, 0x2f, 0xe9, 0xdd, 0x3d, 0xfe, 0xab, 0xa9, 0xaa, 0xd2, 0xa6, 0x40, 0x2a, 0xb2, 0x71, 0x00, 0x01, 0xea, 0x0d, 0xe8, 0x3a, 0x64, 0x25, 0x16, 0x1c, 0x8b, 0xd9, 0x51, 0x39, 0x28, 0x12, 0x51, 0x41, 0xfd, 0xa3, 0xd2, 0xb9, 0x4f, 0x0d, 0x33, 0xb5, 0xf4, 0x87, 0x9d, 0x79, 0x0e, 0xb4, 0xaf, 0x6a, 0xf8, 0xf1, 0xf0, 0xc9, 0xda, 0xbf, 0xff, 0xd6, 0xf2, 0xc6, 0xb5, 0x68, 0x64, 0xd0, 0x6d, 0x35, 0x20, 0x39, 0xcd, 0x13, 0x0f, 0x5e, 0x61, 0xfc, 0x8f, 0x40, 0x8b, 0x5e, 0xe0, 0x66, 0x1c, 0x4f, 0xaa, 0x9d, 0xe6, 0xa6, 0x1e, 0x91, 0x2e, 0xa9, 0x87, 0x95, 0xee, 0x9c, 0xc5, 0x55, 0x34, 0x60, 0x40, 0xae, 0x57, 0x30, 0xd9, 0xa7, 0x95, 0xbd, 0x6f, 0xcb, 0x26, 0x39, 0x40, 0x0d, 0x4e, 0xc0, 0x9f, 0x9e, 0x50, 0x5d, 0xac, 0x79, 0x33, 0x8b, 0xbb, 0x9b, 0x3b, 0x6b, 0x35, 0x48, 0x54, 0x09, 0x29, 0x56, 0x7f, 0xe1, 0x86, 0x72, 0x00, 0x2c, 0x6e, 0xf7, 0x63, 0x3e, 0x63, 0xbd, 0xbd, 0x5d, 0x20, 0x2a, 0xb3, 0xa4, 0x33, 0x48, 0xab, 0x21, 0x43, 0xf1, 0x2c, 0x47, 0xed, 0x1d, 0xbc, 0x73, 0x18, 0x9b, 0x64, 0x28, 0x96, 0x3a, 0xc7, 0x49, 0xb0, 0xf4, 0xcc, 0xe9, 0x73, 0x6c, 0xb4, 0xf8, 0x67, 0x92, 0x32, 0x21, 0x70, 0x7b, 0x89, 0x05, 0x57, 0xef, 0x38, 0x28, 0x94, 0x4a, 0x7d, 0x13, 0x7d, 0x6a, 0xd3, 0x4c, 0xb8, 0xf2, 0xc3, 0xc8, 0x2e, 0x03, 0xf3, 0xe2, 0x7d, 0x33, 0xb7, 0xc5, 0xcc, 0x71, 0x03, 0xc6, 0xb9, 0x64, 0x06, 0xe2, 0x9a, 0xf2, 0x4f, 0xd2, 0x6d, 0xe9, 0xfe, 0x41, 0x45, 0x5b, 0x18, 0x66, 0xa5, 0x64, 0x09, 0xf4, 0xd5, 0xb7, 0xcd, 0x93, 0xc7, 0xcf, 0x9b, 0xe5, 0x6f, 0xf9, 0xc8, 0x0d, 0x56, 0xeb, 0x59, 0xfc, 0xce, 0xd5, 0x12, 0x61, 0xc4, 0x69, 0xe9, 0x0d, 0xa4, 0x4b, 0xfe, 0x48, 0x40, 0xd5, 0x3e, 0xe4, 0xb6, 0x64, 0x8e, 0x4c, 0x02, 0x61, 0x65, 0xa0, 0x14, 0xb4, 0xb6, 0xb0, 0xb1, 0xb6, 0xb2, 0x97, 0xcb, 0xf1, 0x5a, 0x2d, 0xc6, 0xa5, 0xac, 0xb4, 0x70, 0x5d, 0xc7, 0x3d, 0xc1, 0x51, 0x24, 0x91, 0xc9, 0x31, 0x75, 0x6b, 0x70, 0x9f, 0x14, 0x68, 0x01, 0x46, 0xe4, 0xb5, 0xa3, 0x17, 0xcb, 0x40, 0x61, 0x6f, 0x47, 0xff, 0x00, 0x9c, 0x3a, 0x8f, 0x5b, 0x4f, 0x3c, 0x6b, 0xb7, 0xfa, 0x30, 0x91, 0x3c, 0xa4, 0xb1, 0x95, 0xb9, 0x82, 0x42, 0x0a, 0xbc, 0x8e, 0xe4, 0xdb, 0xa9, 0xef, 0xc9, 0x17, 0x91, 0x24, 0x7c, 0xb2, 0x05, 0x64, 0xfb, 0x75, 0x64, 0x32, 0x39, 0x69, 0x5b, 0x9c, 0xad, 0xb9, 0xdb, 0xa7, 0xb5, 0x3b, 0x53, 0x2a, 0x21, 0x41, 0x44, 0xf3, 0x8b, 0x8f, 0x2e, 0x43, 0x9d, 0x2b, 0xd4, 0x57, 0x23, 0x41, 0x36, 0xff, 0x00, 0xff, 0xd7, 0xf0, 0xc0, 0xd5, 0xb5, 0x11, 0x64, 0xb6, 0x3f, 0x59, 0x90, 0xd9, 0xab, 0x06, 0xf4, 0x79, 0x7c, 0x3b, 0x74, 0xc8, 0x08, 0x8b, 0xb6, 0xe3, 0x96, 0x55, 0x57, 0xb3, 0x3e, 0xf2, 0x35, 0xc7, 0xd6, 0x0b, 0x45, 0x5d, 0xdc, 0x8a, 0x7d, 0xd9, 0x8d, 0x94, 0x3b, 0x3d, 0x1c, 0x9e, 0xc3, 0xe5, 0xc3, 0x2c, 0x7c, 0xc5, 0x0f, 0xee, 0xdb, 0x8b, 0x0c, 0xc4, 0x26, 0x9d, 0xa0, 0x9a, 0x7d, 0x2c, 0xe5, 0xe4, 0x55, 0x7f, 0xee, 0xc1, 0x15, 0x04, 0xd0, 0x12, 0x3c, 0x72, 0x89, 0x1b, 0x2c, 0xcc, 0xa8, 0x2a, 0x8b, 0x87, 0xbb, 0x63, 0x1a, 0x28, 0x65, 0xf0, 0xed, 0xf2, 0xc3, 0xc2, 0x0a, 0x06, 0x4a, 0x46, 0xc7, 0xa5, 0xa3, 0x59, 0xc8, 0xb2, 0xc7, 0x45, 0x22, 0x9c, 0x14, 0x54, 0x10, 0x46, 0xf5, 0x1d, 0x32, 0x5c, 0x14, 0x14, 0xe4, 0x32, 0x2f, 0x3a, 0xf3, 0xb6, 0x90, 0x9a, 0x6d, 0xae, 0x9f, 0x3d, 0xab, 0xb8, 0x8a, 0x3b, 0xf8, 0x39, 0x44, 0x58, 0xf0, 0x08, 0xd5, 0x14, 0xa5, 0x7b, 0x65, 0x98, 0x8e, 0xfb, 0xb5, 0x67, 0x87, 0xa5, 0xef, 0x5e, 0x44, 0x96, 0x35, 0xb5, 0xb6, 0x59, 0x36, 0xfd, 0xd8, 0xa0, 0xf1, 0x20, 0x53, 0x33, 0xc0, 0x79, 0x59, 0x73, 0x7c, 0xd7, 0xf9, 0xfb, 0xa2, 0xcd, 0x67, 0xf9, 0xa7, 0x7b, 0x72, 0xf1, 0x71, 0x83, 0x53, 0x86, 0x0b, 0x98, 0x24, 0x22, 0x8a, 0xcc, 0x88, 0x23, 0x7f, 0xb8, 0xae, 0xf9, 0x7c, 0x50, 0x1e, 0x5f, 0x7c, 0x48, 0x21, 0x44, 0x6b, 0xce, 0x9b, 0xb0, 0x1b, 0x9e, 0xf5, 0xaf, 0x8e, 0x4d, 0x5f, 0x7a, 0x7f, 0xce, 0x34, 0xf9, 0x5d, 0x3c, 0xa3, 0xf9, 0x69, 0x63, 0xa9, 0x3c, 0x27, 0xeb, 0xda, 0xe1, 0x37, 0xd7, 0x2e, 0xaa, 0xdb, 0x06, 0xda, 0x30, 0x49, 0xfe, 0x54, 0x03, 0x03, 0x49, 0xdc, 0xb3, 0xaf, 0x38, 0xfe, 0x6a, 0xf9, 0x47, 0xc9, 0x3a, 0x74, 0x97, 0xfa, 0xf6, 0xaf, 0x15, 0x85, 0xb8, 0x75, 0x89, 0xb8, 0x87, 0x9a, 0x72, 0xee, 0x2a, 0x14, 0x24, 0x60, 0xb1, 0xa8, 0xdf, 0x07, 0x0b, 0x2d, 0xcb, 0xcf, 0x7f, 0xe8, 0x6a, 0xff, 0x00, 0x26, 0xbd, 0x6a, 0x7f, 0x89, 0x2f, 0xf8, 0x52, 0x9e, 0xb7, 0xe8, 0xb9, 0xb8, 0x57, 0xc2, 0x95, 0xe9, 0x8f, 0x08, 0x5a, 0x2f, 0xff, 0xd0, 0xf0, 0x4d, 0x40, 0xaa, 0xd7, 0x00, 0x64, 0xcb, 0x3c, 0x97, 0xa8, 0xb5, 0x9e, 0xa3, 0x1a, 0xd6, 0x84, 0x95, 0x3f, 0x45, 0x72, 0x9c, 0xa2, 0xc3, 0x99, 0xa5, 0x9d, 0x49, 0xf4, 0x17, 0x97, 0xaf, 0x63, 0x17, 0x52, 0x6f, 0xf0, 0xc8, 0x43, 0x6f, 0x9a, 0xe9, 0x07, 0x70, 0x0e, 0xec, 0x83, 0x51, 0x44, 0xb8, 0x61, 0x1a, 0x9e, 0x11, 0xd3, 0x91, 0x60, 0x68, 0x6b, 0xd3, 0x31, 0x4f, 0x36, 0xd3, 0x4c, 0x52, 0xef, 0x4c, 0xd5, 0x0c, 0xc4, 0x69, 0xda, 0x94, 0xc8, 0x3a, 0xf0, 0x66, 0x07, 0x73, 0xe0, 0x40, 0xfd, 0x79, 0x93, 0x12, 0x1c, 0x9c, 0x32, 0xc7, 0xfc, 0x41, 0x33, 0xd2, 0xb4, 0x6f, 0x38, 0x98, 0x65, 0x76, 0xbf, 0x69, 0x42, 0xd0, 0xaa, 0xc9, 0xde, 0x95, 0xad, 0x28, 0x46, 0x4e, 0xac, 0x39, 0x77, 0x80, 0x11, 0xbf, 0xd8, 0xc7, 0x7c, 0xe1, 0xa5, 0xf9, 0x92, 0x4d, 0x32, 0x5b, 0x8b, 0x93, 0x27, 0xa7, 0x68, 0x56, 0xe2, 0x45, 0xda, 0x85, 0x61, 0x6e, 0x67, 0xad, 0x6b, 0xb0, 0x38, 0xc2, 0x81, 0xe4, 0xc7, 0x52, 0x31, 0x1c, 0x67, 0x86, 0x5b, 0xbd, 0x37, 0xca, 0x7a, 0x94, 0xb1, 0x69, 0xb6, 0x2e, 0xb7, 0x15, 0x48, 0xc2, 0xb4, 0x52, 0x53, 0xac, 0x32, 0xaf, 0xb1, 0xed, 0x9b, 0x10, 0x36, 0x78, 0x5c, 0x9f, 0x51, 0x64, 0x1f, 0x98, 0x3e, 0x58, 0xb6, 0xfc, 0xc8, 0xf2, 0xe5, 0xbc, 0x68, 0x52, 0x2d, 0x5a, 0xd1, 0x84, 0xb6, 0xf3, 0x95, 0x0e, 0xc0, 0x85, 0xe2, 0xcb, 0xd8, 0xd1, 0xbb, 0xe4, 0xc1, 0xa6, 0x97, 0xce, 0x17, 0x5f, 0x95, 0xde, 0x6d, 0xb6, 0xbe, 0xb7, 0x69, 0x34, 0xf3, 0x3c, 0x72, 0xcf, 0xe8, 0xa3, 0x45, 0x49, 0x95, 0x4a, 0x90, 0x3e, 0x35, 0x5a, 0x95, 0x1d, 0xfe, 0x21, 0x93, 0x4d, 0xbe, 0xd2, 0xd2, 0xf5, 0x8b, 0xbd, 0x32, 0x2d, 0x3f, 0x4c, 0x9a, 0xe4, 0xca, 0x9e, 0x90, 0x85, 0x65, 0x55, 0x08, 0x85, 0x91, 0x01, 0x3b, 0x0a, 0x05, 0xe9, 0xb0, 0xc0, 0x5a, 0xc3, 0xcd, 0x3f, 0x3b, 0x7f, 0x26, 0xec, 0xff, 0x00, 0x35, 0x6d, 0x6d, 0xb5, 0x3d, 0x16, 0xfe, 0x0d, 0x3b, 0xcd, 0x96, 0x01, 0x92, 0x46, 0x9e, 0xa2, 0x0b, 0xc8, 0xb7, 0x28, 0x92, 0x71, 0xfb, 0x2e, 0xa7, 0xec, 0x3d, 0x0f, 0xc2, 0x68, 0x71, 0x05, 0x95, 0xd3, 0xe7, 0x9f, 0xfa, 0x16, 0x2f, 0xcd, 0x7f, 0x43, 0xd6, 0xfa, 0xa5, 0x97, 0xab, 0xeb, 0x7a, 0x5f, 0x55, 0xfa, 0xec, 0x5e, 0xaf, 0x0f, 0xf7, 0xed, 0x2b, 0x4e, 0x15, 0xff, 0x00, 0x65, 0xdf, 0x8e, 0x14, 0xf1, 0xbf, 0xff, 0xd1, 0xf0, 0x5a, 0xa7, 0x18, 0x5e, 0x56, 0x1f, 0x68, 0x71, 0x5f, 0xa7, 0xbe, 0x2a, 0x98, 0xdb, 0xfa, 0x90, 0x24, 0x37, 0xb0, 0xfd, 0xb8, 0xa8, 0x58, 0x78, 0xae, 0x43, 0xc9, 0xb4, 0x6d, 0xbb, 0xda, 0x3c, 0xa1, 0xad, 0x43, 0xa8, 0xda, 0xc5, 0x2a, 0x3d, 0x26, 0x5a, 0x02, 0x2b, 0xbe, 0x60, 0x64, 0x8d, 0x17, 0x6f, 0x8b, 0x20, 0x90, 0x7a, 0x3c, 0x32, 0x8b, 0xa8, 0x02, 0xf3, 0xfd, 0xe0, 0x1b, 0x11, 0x98, 0x66, 0x3b, 0xb9, 0x62, 0x54, 0x83, 0x36, 0xf2, 0xa4, 0xe4, 0x29, 0x34, 0xeb, 0xc8, 0x74, 0xae, 0x0d, 0xc3, 0x65, 0x82, 0x13, 0x6b, 0x57, 0xba, 0x54, 0xe4, 0x8c, 0x41, 0x1b, 0x75, 0xa7, 0xe0, 0x72, 0x5c, 0x4c, 0x84, 0x50, 0x5a, 0xb3, 0xdd, 0xdd, 0xc3, 0x24, 0x33, 0xb1, 0x60, 0xe0, 0x86, 0x52, 0x45, 0x38, 0xd2, 0x87, 0x24, 0x26, 0x6d, 0x8c, 0xe1, 0x41, 0x25, 0xfc, 0xa3, 0xd7, 0x2f, 0x6f, 0x3c, 0xbf, 0x73, 0xa5, 0xb2, 0x2c, 0xd1, 0x69, 0x17, 0x2f, 0x6b, 0x14, 0x8c, 0x0f, 0x21, 0x0d, 0x79, 0x46, 0x09, 0x15, 0xed, 0xb7, 0x4e, 0xd9, 0xb9, 0x8b, 0xcb, 0xe4, 0xa2, 0x5e, 0xa3, 0xa6, 0xdf, 0x6a, 0x36, 0xe4, 0xcd, 0x69, 0x1c, 0x4e, 0x84, 0x7c, 0x76, 0xab, 0x21, 0x67, 0xa8, 0xa7, 0xd9, 0xf8, 0x4d, 0x2b, 0xf3, 0xc3, 0x4d, 0x49, 0x57, 0x98, 0x75, 0x6f, 0x31, 0xda, 0xf9, 0xa3, 0x4b, 0xfd, 0x1f, 0x69, 0x1d, 0xae, 0xa1, 0xa9, 0x7e, 0xee, 0xe6, 0xd2, 0x79, 0x18, 0xf3, 0xb5, 0x1f, 0xee, 0xd9, 0x0a, 0x01, 0x4e, 0x3f, 0xb3, 0x4d, 0xf2, 0x9c, 0xb9, 0x04, 0x05, 0xb7, 0xe2, 0x87, 0x1e, 0xdd, 0x19, 0x3e, 0xaf, 0x6b, 0xae, 0xcb, 0x6d, 0x13, 0x0d, 0x45, 0xa2, 0x8e, 0x06, 0xe5, 0x13, 0x2a, 0x02, 0x01, 0x5e, 0x82, 0xb5, 0x04, 0xe6, 0x11, 0xd4, 0xcd, 0xda, 0x43, 0x49, 0x8e, 0xb7, 0xdc, 0xb1, 0x51, 0xe6, 0x4d, 0x76, 0xd2, 0x61, 0x15, 0xaa, 0x4b, 0xa8, 0xc9, 0x6e, 0x49, 0x79, 0x20, 0xe6, 0x8c, 0x49, 0xad, 0x43, 0x16, 0xe4, 0xa7, 0xaf, 0x43, 0xd3, 0x26, 0x35, 0x75, 0xcd, 0xa8, 0xe8, 0x87, 0x46, 0xbf, 0xc7, 0x9a, 0xff, 0x00, 0xd6, 0xbf, 0x48, 0xfe, 0x88, 0xfd, 0xe7, 0x0f, 0xab, 0xfa, 0x3f, 0x58, 0x7f, 0x5f, 0x8d, 0x3f, 0x9f, 0xa7, 0x5e, 0xd4, 0xc3, 0xf9, 0xd1, 0x7c, 0xb6, 0x47, 0xe4, 0x3a, 0x5b, 0xff, 0xd2, 0xf0, 0xb7, 0xa6, 0x1e, 0xdf, 0xd3, 0xf6, 0xa5, 0x71, 0x54, 0xdb, 0x4b, 0x80, 0x3c, 0x42, 0x26, 0xee, 0x29, 0xbe, 0x51, 0x23, 0x4e, 0x44, 0x05, 0x84, 0x45, 0xa5, 0xd5, 0xf7, 0x97, 0x2e, 0xfd, 0x6b, 0x6a, 0x98, 0x09, 0xab, 0xc7, 0xfc, 0x46, 0x3b, 0x4c, 0x26, 0x32, 0x30, 0x3e, 0x4f, 0x49, 0xd0, 0xfc, 0xfb, 0x05, 0xd4, 0x4a, 0x7d, 0x40, 0xac, 0x3a, 0x8e, 0x84, 0x1c, 0xc5, 0x96, 0x2a, 0x73, 0xe1, 0x9c, 0x16, 0x6d, 0xa5, 0x79, 0x86, 0xd6, 0xec, 0x80, 0x5a, 0xa0, 0xf5, 0xca, 0xcc, 0x5c, 0xa1, 0x2b, 0x1b, 0x26, 0x30, 0x6a, 0x31, 0x46, 0xcf, 0x1c, 0x87, 0x94, 0x64, 0x9e, 0x3d, 0xb6, 0xf0, 0xca, 0xa8, 0x39, 0x51, 0x99, 0x42, 0x6b, 0x1a, 0xc5, 0xa5, 0xa5, 0x94, 0xf7, 0x92, 0xc8, 0xaa, 0xb1, 0x23, 0x30, 0x04, 0xf8, 0x0e, 0x9f, 0x4e, 0x4a, 0x11, 0xb2, 0xd5, 0x9b, 0x25, 0x06, 0x1b, 0xff, 0x00, 0x38, 0xfd, 0xad, 0xdf, 0xda, 0xf9, 0xa2, 0xfe, 0xc5, 0x42, 0xbe, 0x9b, 0x7f, 0x0b, 0xdd, 0xdd, 0x07, 0xaf, 0x14, 0x68, 0xd8, 0x71, 0x6d, 0xbb, 0x90, 0xfc, 0x73, 0x6e, 0xf2, 0xf2, 0xdd, 0xf4, 0xad, 0xa6, 0xab, 0x6d, 0x69, 0x14, 0xfa, 0xee, 0xa0, 0xe2, 0x0b, 0x0d, 0x39, 0x19, 0xfe, 0x11, 0xc5, 0x1a, 0x4a, 0x1d, 0x8f, 0x73, 0x4f, 0xf8, 0x96, 0x0b, 0x40, 0x8d, 0xec, 0xf3, 0x6d, 0x3f, 0x52, 0xba, 0xd6, 0x35, 0x8b, 0xbf, 0x36, 0x6a, 0x5f, 0x0d, 0xc5, 0xdc, 0xa8, 0xb6, 0xa8, 0x7a, 0xc5, 0x6c, 0x9b, 0x22, 0x0f, 0xa3, 0x73, 0x9a, 0xbc, 0xb3, 0xe2, 0x36, 0xed, 0xb1, 0x43, 0x80, 0x53, 0xd0, 0xa7, 0xd4, 0x44, 0xfa, 0x7a, 0xda, 0x83, 0xbd, 0x3e, 0x2f, 0xa7, 0x2b, 0xad, 0x9b, 0xb8, 0x8d, 0xa8, 0xe8, 0x91, 0xdb, 0xfa, 0x2d, 0x6f, 0xc3, 0x8a, 0x2d, 0x56, 0xa3, 0xad, 0x4f, 0x5c, 0xa4, 0x0d, 0xdc, 0xa3, 0xca, 0xd0, 0xbf, 0xa1, 0xe3, 0xfa, 0xe7, 0x0f, 0xf2, 0xb9, 0x57, 0xbf, 0x1a, 0xe4, 0xb8, 0x57, 0xc5, 0xdd, 0xff, 0xd3, 0xf0, 0xcc, 0x5d, 0x7b, 0x70, 0xc5, 0x53, 0x6d, 0x2f, 0xd5, 0xe4, 0x69, 0xfd, 0xdf, 0xec, 0xd7, 0xad, 0x7d, 0xb2, 0x8c, 0x8d, 0xd8, 0xed, 0x91, 0x9f, 0x43, 0xea, 0xe7, 0xeb, 0x94, 0xad, 0x3e, 0x1e, 0x95, 0xfc, 0x72, 0x81, 0x7d, 0x1c, 0x9d, 0xba, 0xb1, 0x7b, 0xdf, 0xa9, 0x7a, 0xdf, 0xee, 0x2f, 0xd4, 0xfa, 0xe7, 0xed, 0x7a, 0x7f, 0xdd, 0xff, 0x00, 0xb2, 0xae, 0x64, 0x0b, 0xea, 0xe3, 0x9a, 0xbf, 0x4a, 0x6f, 0xa4, 0xff, 0x00, 0x89, 0xbd, 0x45, 0xfa, 0xb5, 0x79, 0xf7, 0xeb, 0xc7, 0xe9, 0xae, 0x57, 0x2e, 0x17, 0x23, 0x1f, 0x89, 0xd1, 0x99, 0x8f, 0xf1, 0xa7, 0x11, 0xcf, 0xd3, 0xf5, 0x29, 0xb5, 0x6b, 0xd3, 0xe8, 0xcc, 0x7f, 0x45, 0xb9, 0xa3, 0xc5, 0x62, 0xbe, 0x68, 0xff, 0x00, 0x15, 0xfd, 0x4c, 0xfe, 0x90, 0xaf, 0xd4, 0xab, 0xf1, 0x7a, 0x7f, 0x62, 0x9d, 0xab, 0xdf, 0x32, 0xb1, 0x70, 0x5e, 0xdc, 0xdc, 0x2d, 0x47, 0x8b, 0x5e, 0xae, 0x4c, 0xbf, 0xf2, 0x37, 0x9f, 0x3d, 0x5b, 0xd2, 0xff, 0x00, 0x8e, 0x87, 0xee, 0x29, 0x5a, 0xf2, 0xf4, 0xaa, 0xd4, 0xa5, 0x36, 0xa7, 0x3a, 0x57, 0xfd, 0x8e, 0x64, 0x3a, 0xf2, 0xf6, 0xbf, 0xcc, 0x7f, 0x5b, 0xfc, 0x23, 0xa7, 0xfe, 0x8e, 0xff, 0x00, 0x8e, 0x37, 0xd6, 0x63, 0xfa, 0xe5, 0x2b, 0xcb, 0x87, 0xec, 0xd6, 0xbd, 0xb9, 0x7d, 0xac, 0xc7, 0xcd, 0x7c, 0x2d, 0xf8, 0x2b, 0x89, 0x26, 0x8f, 0xd4, 0xfa, 0x94, 0x3e, 0x85, 0x29, 0xc9, 0x69, 0xfc, 0x33, 0x58, 0x5d, 0x9c, 0x79, 0xb2, 0xbb, 0x0f, 0xac, 0x7a, 0x2b, 0xea, 0x75, 0xef, 0x92, 0x0c, 0x53, 0x3d, 0x2f, 0xd4, 0xfa, 0xbb, 0xfa, 0x74, 0xf5, 0x39, 0x9a, 0xd7, 0xe7, 0x80, 0x53, 0x79, 0xba, 0x5b, 0xfe, 0x97, 0xfa, 0x4b, 0xfc, 0xba, 0x7f, 0xb1, 0xc7, 0xab, 0x1e, 0x8f, 0xff, 0xd9
+};
diff --git a/talk/base/testclient.cc b/talk/base/testclient.cc
new file mode 100644
index 0000000..8a605ce
--- /dev/null
+++ b/talk/base/testclient.cc
@@ -0,0 +1,137 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/testclient.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+
+// DESIGN: Each packet received is put it into a list of packets.
+//         Callers can retrieve received packets from any thread by calling
+//         NextPacket.
+
+TestClient::TestClient(AsyncPacketSocket* socket) : socket_(socket) {
+  packets_ = new std::vector<Packet*>();
+  socket_->SignalReadPacket.connect(this, &TestClient::OnPacket);
+}
+
+TestClient::~TestClient() {
+  delete socket_;
+  for (unsigned i = 0; i < packets_->size(); i++)
+    delete (*packets_)[i];
+  delete packets_;
+}
+
+bool TestClient::CheckConnState(AsyncPacketSocket::State state) {
+  // Wait for our timeout value until the socket reaches the desired state.
+  uint32 end = TimeAfter(kTimeout);
+  while (socket_->GetState() != state && TimeUntil(end) > 0)
+    Thread::Current()->ProcessMessages(1);
+  return (socket_->GetState() == state);
+}
+
+int TestClient::Send(const char* buf, size_t size) {
+  return socket_->Send(buf, size);
+}
+
+int TestClient::SendTo(const char* buf, size_t size,
+                       const SocketAddress& dest) {
+  return socket_->SendTo(buf, size, dest);
+}
+
+TestClient::Packet* TestClient::NextPacket() {
+  // If no packets are currently available, we go into a get/dispatch loop for
+  // at most 1 second.  If, during the loop, a packet arrives, then we can stop
+  // early and return it.
+
+  // Note that the case where no packet arrives is important.  We often want to
+  // test that a packet does not arrive.
+
+  // Note also that we only try to pump our current thread's message queue.
+  // Pumping another thread's queue could lead to messages being dispatched from
+  // the wrong thread to non-thread-safe objects.
+
+  uint32 end = TimeAfter(kTimeout);
+  while (packets_->size() == 0 && TimeUntil(end) > 0)
+    Thread::Current()->ProcessMessages(1);
+
+  // Return the first packet placed in the queue.
+  Packet* packet = NULL;
+  if (packets_->size() > 0) {
+    CritScope cs(&crit_);
+    packet = packets_->front();
+    packets_->erase(packets_->begin());
+  }
+
+  return packet;
+}
+
+bool TestClient::CheckNextPacket(const char* buf, size_t size,
+                                 SocketAddress* addr) {
+  bool res = false;
+  Packet* packet = NextPacket();
+  if (packet) {
+    res = (packet->size == size && std::memcmp(packet->buf, buf, size) == 0);
+    if (addr)
+      *addr = packet->addr;
+    delete packet;
+  }
+  return res;
+}
+
+bool TestClient::CheckNoPacket() {
+  bool res;
+  Packet* packet = NextPacket();
+  res = (packet == NULL);
+  delete packet;
+  return res;
+}
+
+void TestClient::OnPacket(AsyncPacketSocket* socket, const char* buf,
+                          size_t size, const SocketAddress& remote_addr) {
+  CritScope cs(&crit_);
+  packets_->push_back(new Packet(remote_addr, buf, size));
+}
+
+TestClient::Packet::Packet(const SocketAddress& a, const char* b, size_t s)
+    : addr(a), buf(0), size(s) {
+  buf = new char[size];
+  memcpy(buf, b, size);
+}
+
+TestClient::Packet::Packet(const Packet& p)
+    : addr(p.addr), buf(0), size(p.size) {
+  buf = new char[size];
+  memcpy(buf, p.buf, size);
+}
+
+TestClient::Packet::~Packet() {
+  delete[] buf;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/testclient.h b/talk/base/testclient.h
new file mode 100644
index 0000000..400e58f
--- /dev/null
+++ b/talk/base/testclient.h
@@ -0,0 +1,102 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_TESTCLIENT_H_
+#define TALK_BASE_TESTCLIENT_H_
+
+#include <vector>
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+// A simple client that can send TCP or UDP data and check that it receives
+// what it expects to receive. Useful for testing server functionality.
+class TestClient : public sigslot::has_slots<> {
+ public:
+  // Records the contents of a packet that was received.
+  struct Packet {
+    Packet(const SocketAddress& a, const char* b, size_t s);
+    Packet(const Packet& p);
+    virtual ~Packet();
+
+    SocketAddress addr;
+    char*  buf;
+    size_t size;
+  };
+
+  // Creates a client that will send and receive with the given socket and
+  // will post itself messages with the given thread.
+  explicit TestClient(AsyncPacketSocket* socket);
+  ~TestClient();
+
+  SocketAddress address() const { return socket_->GetLocalAddress(); }
+  SocketAddress remote_address() const { return socket_->GetRemoteAddress(); }
+
+  // Checks that the socket moves to the specified connect state.
+  bool CheckConnState(AsyncPacketSocket::State state);
+
+  // Checks that the socket is connected to the remote side.
+  bool CheckConnected() {
+    return CheckConnState(AsyncPacketSocket::STATE_CONNECTED);
+  }
+
+  // Sends using the clients socket.
+  int Send(const char* buf, size_t size);
+
+  // Sends using the clients socket to the given destination.
+  int SendTo(const char* buf, size_t size, const SocketAddress& dest);
+
+  // Returns the next packet received by the client or 0 if none is received
+  // within a reasonable amount of time.  The caller must delete the packet
+  // when done with it.
+  Packet* NextPacket();
+
+  // Checks that the next packet has the given contents. Returns the remote
+  // address that the packet was sent from.
+  bool CheckNextPacket(const char* buf, size_t len, SocketAddress* addr);
+
+  // Checks that no packets have arrived or will arrive in the next second.
+  bool CheckNoPacket();
+
+ private:
+  static const int kTimeout = 1000;
+  // Workaround for the fact that AsyncPacketSocket::GetConnState doesn't exist.
+  Socket::ConnState GetState();
+  // Slot for packets read on the socket.
+  void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t len,
+                const SocketAddress& remote_addr);
+
+  CriticalSection crit_;
+  AsyncPacketSocket* socket_;
+  std::vector<Packet*>* packets_;
+  DISALLOW_EVIL_CONSTRUCTORS(TestClient);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_TESTCLIENT_H_
diff --git a/talk/base/testclient_unittest.cc b/talk/base/testclient_unittest.cc
new file mode 100644
index 0000000..ee91067
--- /dev/null
+++ b/talk/base/testclient_unittest.cc
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/host.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/testclient.h"
+#include "talk/base/testechoserver.h"
+#include "talk/base/thread.h"
+
+using namespace talk_base;
+
+static const SocketAddress kLocalHostAnyAddr("127.0.0.1", 0);
+
+// Tests whether the TestClient can send UDP to itself.
+TEST(TestClientTest, TestUdp) {
+  Thread *main = Thread::Current();
+  AsyncSocket* socket = main->socketserver()->CreateAsyncSocket(SOCK_DGRAM);
+  socket->Bind(kLocalHostAnyAddr);
+
+  TestClient client(new AsyncUDPSocket(socket));
+  SocketAddress addr = client.address(), from;
+  EXPECT_EQ(3, client.SendTo("foo", 3, addr));
+  EXPECT_TRUE(client.CheckNextPacket("foo", 3, &from));
+  EXPECT_EQ(from, addr);
+  EXPECT_TRUE(client.CheckNoPacket());
+}
+
+// Tests whether the TestClient can connect to a server and exchange data.
+TEST(TestClientTest, TestTcp) {
+  Thread *main = Thread::Current();
+  TestEchoServer server(main, kLocalHostAnyAddr);
+
+  AsyncSocket* socket = main->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+  AsyncTCPSocket* tcp_socket = AsyncTCPSocket::Create(
+      socket, kLocalHostAnyAddr, server.address());
+  ASSERT_TRUE(tcp_socket != NULL);
+
+  TestClient client(tcp_socket);
+  SocketAddress addr = client.address(), from;
+  EXPECT_TRUE(client.CheckConnected());
+  EXPECT_EQ(3, client.Send("foo", 3));
+  EXPECT_TRUE(client.CheckNextPacket("foo", 3, &from));
+  EXPECT_EQ(from, server.address());
+  EXPECT_TRUE(client.CheckNoPacket());
+}
diff --git a/talk/base/thread.cc b/talk/base/thread.cc
index 5e0eec4..8b11485 100644
--- a/talk/base/thread.cc
+++ b/talk/base/thread.cc
@@ -36,7 +36,7 @@
 #include "talk/base/common.h"
 #include "talk/base/logging.h"
 #include "talk/base/stringutils.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 
 #ifdef USE_COCOA_THREADING
 #if !defined(OSX) && !defined(IOS)
@@ -48,14 +48,15 @@
 
 namespace talk_base {
 
-ThreadManager g_thmgr;
+ThreadManager* ThreadManager::Instance() {
+  LIBJINGLE_DEFINE_STATIC_LOCAL(ThreadManager, thread_manager, ());
+  return &thread_manager;
+}
 
 #ifdef POSIX
-pthread_key_t ThreadManager::key_;
-
 ThreadManager::ThreadManager() {
   pthread_key_create(&key_, NULL);
-  main_thread_ = WrapCurrentThread();
+  WrapCurrentThread();
 #ifdef USE_COCOA_THREADING
   InitCocoaMultiThreading();
 #endif
@@ -69,7 +70,6 @@
   ScopedAutoreleasePool pool;
 #endif
   UnwrapCurrentThread();
-  // Unwrap deletes main_thread_ automatically.
   pthread_key_delete(key_);
 }
 
@@ -77,17 +77,15 @@
   return static_cast<Thread *>(pthread_getspecific(key_));
 }
 
-void ThreadManager::SetCurrent(Thread *thread) {
+void ThreadManager::SetCurrentThread(Thread *thread) {
   pthread_setspecific(key_, thread);
 }
 #endif
 
 #ifdef WIN32
-DWORD ThreadManager::key_;
-
 ThreadManager::ThreadManager() {
   key_ = TlsAlloc();
-  main_thread_ = WrapCurrentThread();
+  WrapCurrentThread();
 }
 
 ThreadManager::~ThreadManager() {
@@ -99,7 +97,7 @@
   return static_cast<Thread *>(TlsGetValue(key_));
 }
 
-void ThreadManager::SetCurrent(Thread *thread) {
+void ThreadManager::SetCurrentThread(Thread *thread) {
   TlsSetValue(key_, thread);
 }
 #endif
@@ -109,7 +107,7 @@
   Thread* result = CurrentThread();
   if (NULL == result) {
     result = new Thread();
-    result->WrapCurrent();
+    result->WrapCurrentWithThreadManager(this);
   }
   return result;
 }
@@ -123,25 +121,6 @@
   }
 }
 
-void ThreadManager::Add(Thread *thread) {
-  CritScope cs(&crit_);
-  threads_.push_back(thread);
-}
-
-void ThreadManager::Remove(Thread *thread) {
-  CritScope cs(&crit_);
-  threads_.erase(std::remove(threads_.begin(), threads_.end(), thread),
-                 threads_.end());
-}
-
-void ThreadManager::StopAllThreads_() {
-  // TODO: In order to properly implement, Threads need to be ref-counted.
-  CritScope cs(&g_thmgr.crit_);
-  for (size_t i = 0; i < g_thmgr.threads_.size(); ++i) {
-    g_thmgr.threads_[i]->Stop();
-  }
-}
-
 struct ThreadInit {
   Thread* thread;
   Runnable* runnable;
@@ -157,7 +136,6 @@
 #endif
       owned_(true),
       delete_self_when_complete_(false) {
-  g_thmgr.Add(this);
   SetName("Thread", this);  // default name
 }
 
@@ -165,7 +143,6 @@
   Stop();
   if (active_)
     Clear(NULL);
-  g_thmgr.Remove(this);
 }
 
 bool Thread::SleepMs(int milliseconds) {
@@ -231,6 +208,10 @@
   ASSERT(!started_);
   if (started_) return false;
 
+  // Make sure that ThreadManager is created on the main thread before
+  // we start a new thread.
+  ThreadManager::Instance();
+
   ThreadInit* init = new ThreadInit;
   init->thread = this;
   init->runnable = runnable;
@@ -334,7 +315,7 @@
 
 void* Thread::PreRun(void* pv) {
   ThreadInit* init = static_cast<ThreadInit*>(pv);
-  ThreadManager::SetCurrent(init->thread);
+  ThreadManager::Instance()->SetCurrentThread(init->thread);
 #if defined(WIN32)
   SetThreadName(GetCurrentThreadId(), init->thread->name_.c_str());
 #elif defined(POSIX)
@@ -509,6 +490,10 @@
 }
 
 bool Thread::WrapCurrent() {
+  return WrapCurrentWithThreadManager(ThreadManager::Instance());
+}
+
+bool Thread::WrapCurrentWithThreadManager(ThreadManager* thread_manager) {
   if (started_)
     return false;
 #if defined(WIN32)
@@ -524,13 +509,13 @@
 #endif
   owned_ = false;
   started_ = true;
-  ThreadManager::SetCurrent(this);
+  thread_manager->SetCurrentThread(this);
   return true;
 }
 
 void Thread::UnwrapCurrent() {
   // Clears the platform-specific thread-specific storage.
-  ThreadManager::SetCurrent(NULL);
+  ThreadManager::Instance()->SetCurrentThread(NULL);
 #ifdef WIN32
   if (!CloseHandle(thread_)) {
     LOG_GLE(LS_ERROR) << "When unwrapping thread, failed to close handle.";
@@ -541,14 +526,14 @@
 
 
 AutoThread::AutoThread(SocketServer* ss) : Thread(ss) {
-  if (!ThreadManager::CurrentThread()) {
-    ThreadManager::SetCurrent(this);
+  if (!ThreadManager::Instance()->CurrentThread()) {
+    ThreadManager::Instance()->SetCurrentThread(this);
   }
 }
 
 AutoThread::~AutoThread() {
-  if (ThreadManager::CurrentThread() == this) {
-    ThreadManager::SetCurrent(NULL);
+  if (ThreadManager::Instance()->CurrentThread() == this) {
+    ThreadManager::Instance()->SetCurrentThread(NULL);
   }
 }
 
diff --git a/talk/base/thread.h b/talk/base/thread.h
index 04ceeef..13fc68c 100644
--- a/talk/base/thread.h
+++ b/talk/base/thread.h
@@ -53,10 +53,10 @@
   ThreadManager();
   ~ThreadManager();
 
-  static Thread *CurrentThread();
-  static void SetCurrent(Thread *thread);
-  void Add(Thread *thread);
-  void Remove(Thread *thread);
+  static ThreadManager* Instance();
+
+  Thread* CurrentThread();
+  void SetCurrentThread(Thread* thread);
 
   // Returns a thread object with its thread_ ivar set
   // to whatever the OS uses to represent the thread.
@@ -71,22 +71,16 @@
   // shame to break it.  It is also conceivable on Win32 that we won't even
   // be able to get synchronization privileges, in which case the result
   // will have a NULL handle.
-  static Thread *WrapCurrentThread();
-  static void UnwrapCurrentThread();
-
-  static void StopAllThreads_();  // Experimental
+  Thread *WrapCurrentThread();
+  void UnwrapCurrentThread();
 
  private:
-  Thread *main_thread_;
-  std::vector<Thread *> threads_;
-  CriticalSection crit_;
-
 #ifdef POSIX
-  static pthread_key_t key_;
+  pthread_key_t key_;
 #endif
 
 #ifdef WIN32
-  static DWORD key_;
+  DWORD key_;
 #endif
 
   DISALLOW_COPY_AND_ASSIGN(ThreadManager);
@@ -126,11 +120,11 @@
   virtual ~Thread();
 
   static inline Thread* Current() {
-    return ThreadManager::CurrentThread();
+    return ThreadManager::Instance()->CurrentThread();
   }
 
   bool IsCurrent() const {
-    return (ThreadManager::CurrentThread() == this);
+    return ThreadManager::Instance()->CurrentThread() == this;
   }
 
   // Sleeps the calling thread for the specified number of milliseconds, during
@@ -215,6 +209,11 @@
  private:
   static void *PreRun(void *pv);
 
+  // ThreadManager calls this instead WrapCurrent() because
+  // ThreadManager::Instance() cannot be used while ThreadManager is
+  // being created.
+  bool WrapCurrentWithThreadManager(ThreadManager* thread_manager);
+
   std::list<_SendMessage> sendlist_;
   std::string name_;
   ThreadPriority priority_;
diff --git a/talk/base/thread_unittest.cc b/talk/base/thread_unittest.cc
index a70b52d..1a3ce75 100644
--- a/talk/base/thread_unittest.cc
+++ b/talk/base/thread_unittest.cc
@@ -124,9 +124,6 @@
   CustomThread() {}
   virtual ~CustomThread() {}
   bool Start() { return false; }
-
-  bool WrapCurrent() { return talk_base::Thread::WrapCurrent(); }
-  void UnwrapCurrent() { talk_base::Thread::UnwrapCurrent(); }
 };
 
 
@@ -220,7 +217,7 @@
 }
 
 TEST(ThreadTest, Wrap) {
-  Thread* current_thread = ThreadManager::CurrentThread();
+  Thread* current_thread = Thread::Current();
   current_thread->UnwrapCurrent();
   CustomThread* cthread = new CustomThread();
   EXPECT_TRUE(cthread->WrapCurrent());
diff --git a/talk/base/time.h b/talk/base/time.h
index eca0e4e..8453ef5 100644
--- a/talk/base/time.h
+++ b/talk/base/time.h
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2005, Google Inc.
+ * Copyright 2005 Google Inc.
  *
  * Redistribution and use in source and binary forms, with or without 
  * modification, are permitted provided that the following conditions are met:
@@ -36,6 +36,15 @@
 
 namespace talk_base {
 
+static const int64 kNumMillisecsPerSec = 1000;
+static const int64 kNumMicrosecsPerSec = 1000000;
+static const int64 kNumNanosecsPerSec = 1000000000;
+
+static const int64 kNumMicrosecsPerMillisec = kNumMicrosecsPerSec /
+    kNumMillisecsPerSec;
+static const int64 kNumNanosecsPerMillisec =  kNumNanosecsPerSec /
+    kNumMillisecsPerSec;
+
 typedef uint32 TimeStamp;
 
 // Returns the current time in milliseconds.
diff --git a/talk/base/timeutils.cc b/talk/base/timeutils.cc
new file mode 100644
index 0000000..98ba0bc
--- /dev/null
+++ b/talk/base/timeutils.cc
@@ -0,0 +1,125 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef POSIX
+#include <sys/time.h>
+#endif
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/timeutils.h"
+
+#define EFFICIENT_IMPLEMENTATION 1
+
+namespace talk_base {
+
+const uint32 LAST = 0xFFFFFFFF;
+const uint32 HALF = 0x80000000;
+
+#ifdef POSIX
+uint32 Time() {
+  struct timeval tv;
+  gettimeofday(&tv, 0);
+  return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+}
+#endif
+
+#ifdef WIN32
+uint32 Time() {
+  return GetTickCount();
+}
+#endif
+
+uint32 StartTime() {
+  // Close to program execution time
+  static const uint32 g_start = Time();
+  return g_start;
+}
+
+// Make sure someone calls it so that it gets initialized
+static uint32 ignore = StartTime();
+
+uint32 TimeAfter(int32 elapsed) {
+  ASSERT(elapsed >= 0);
+  ASSERT(static_cast<uint32>(elapsed) < HALF);
+  return Time() + elapsed;
+}
+
+bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later) {
+  if (earlier <= later) {
+    return ((earlier <= middle) && (middle <= later));
+  } else {
+    return !((later < middle) && (middle < earlier));
+  }
+}
+
+bool TimeIsLaterOrEqual(uint32 earlier, uint32 later) {
+#if EFFICIENT_IMPLEMENTATION
+  int32 diff = later - earlier;
+  return (diff >= 0 && static_cast<uint32>(diff) < HALF);
+#else
+  const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF);
+  return later_or_equal;
+#endif
+}
+
+bool TimeIsLater(uint32 earlier, uint32 later) {
+#if EFFICIENT_IMPLEMENTATION
+  int32 diff = later - earlier;
+  return (diff > 0 && static_cast<uint32>(diff) < HALF);
+#else
+  const bool earlier_or_equal = TimeIsBetween(later, earlier, later + HALF);
+  return !earlier_or_equal;
+#endif
+}
+
+int32 TimeDiff(uint32 later, uint32 earlier) {
+#if EFFICIENT_IMPLEMENTATION
+  return later - earlier;
+#else
+  const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF);
+  if (later_or_equal) {
+    if (earlier <= later) {
+      return static_cast<long>(later - earlier);
+    } else {
+      return static_cast<long>(later + (LAST - earlier) + 1);
+    }
+  } else {
+    if (later <= earlier) {
+      return -static_cast<long>(earlier - later);
+    } else {
+      return -static_cast<long>(earlier + (LAST - later) + 1);
+    }
+  }
+#endif
+}
+
+} // namespace talk_base
diff --git a/talk/base/timeutils.h b/talk/base/timeutils.h
new file mode 100644
index 0000000..713fd77
--- /dev/null
+++ b/talk/base/timeutils.h
@@ -0,0 +1,90 @@
+/*
+ * libjingle
+ * Copyright 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_TIMEUTILS_H_
+#define TALK_BASE_TIMEUTILS_H_
+
+#ifndef WIN32
+#include <time.h>
+#endif
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+static const int64 kNumMillisecsPerSec = 1000;
+static const int64 kNumMicrosecsPerSec = 1000000;
+static const int64 kNumNanosecsPerSec = 1000000000;
+
+static const int64 kNumMicrosecsPerMillisec = kNumMicrosecsPerSec /
+    kNumMillisecsPerSec;
+static const int64 kNumNanosecsPerMillisec =  kNumNanosecsPerSec /
+    kNumMillisecsPerSec;
+
+typedef uint32 TimeStamp;
+
+// Returns the current time in milliseconds.
+uint32 Time();
+
+// Approximate time when the program started.
+uint32 StartTime();
+
+// Returns a future timestamp, 'elapsed' milliseconds from now.
+uint32 TimeAfter(int32 elapsed);
+
+// Comparisons between time values, which can wrap around.
+bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later);  // Inclusive
+bool TimeIsLaterOrEqual(uint32 earlier, uint32 later);  // Inclusive
+bool TimeIsLater(uint32 earlier, uint32 later);  // Exclusive
+
+// Returns the later of two timestamps.
+inline uint32 TimeMax(uint32 ts1, uint32 ts2) {
+  return TimeIsLaterOrEqual(ts1, ts2) ? ts2 : ts1;
+}
+
+// Returns the earlier of two timestamps.
+inline uint32 TimeMin(uint32 ts1, uint32 ts2) {
+  return TimeIsLaterOrEqual(ts1, ts2) ? ts1 : ts2;
+}
+
+// Number of milliseconds that would elapse between 'earlier' and 'later'
+// timestamps.  The value is negative if 'later' occurs before 'earlier'.
+int32 TimeDiff(uint32 later, uint32 earlier);
+
+// The number of milliseconds that have elapsed since 'earlier'.
+inline int32 TimeSince(uint32 earlier) {
+  return TimeDiff(Time(), earlier);
+} 
+
+// The number of milliseconds that will elapse between now and 'later'.
+inline int32 TimeUntil(uint32 later) {
+  return TimeDiff(later, Time());
+}
+
+} // namespace talk_base
+
+#endif // TALK_BASE_TIMEUTILS_H_
diff --git a/talk/base/timeutils_unittest.cc b/talk/base/timeutils_unittest.cc
new file mode 100644
index 0000000..9f315ee
--- /dev/null
+++ b/talk/base/timeutils_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/timeutils.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/timing.cc b/talk/base/timing.cc
new file mode 100644
index 0000000..5e1c6c8
--- /dev/null
+++ b/talk/base/timing.cc
@@ -0,0 +1,152 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/timing.h"
+
+#if defined(POSIX)
+#define _POSIX_C_SOURCE 199506L
+#endif
+
+#include <time.h>
+
+#if defined(POSIX)
+#include <errno.h>
+#include <math.h>
+#include <sys/time.h>
+#if defined(OSX)
+#include <mach/mach.h>
+#include <mach/clock.h>
+#endif
+#elif defined(WIN32)
+#include <sys/timeb.h>
+#include "talk/base/win32.h"
+#endif
+
+Timing::Timing() {
+#if defined(WIN32)
+  QueryPerformanceFrequency(&tick_hz_);
+
+  // This may fail, but we handle failure gracefully in the methods
+  // that use it (use alternative sleep method).
+  //
+  // TODO: Make it possible for user to tell if IdleWait will
+  // be done at lesser resolution because of this.
+  timer_handle_ = CreateWaitableTimer(NULL,     // Security attributes.
+                                      FALSE,    // Manual reset?
+                                      NULL);    // Timer name.
+#endif
+}
+
+Timing::~Timing() {
+#if defined(WIN32)
+  if (timer_handle_ != NULL)
+    CloseHandle(timer_handle_);
+#endif
+}
+
+double Timing::WallTimeNow() {
+#if defined(POSIX)
+  struct timeval time;
+  gettimeofday(&time, NULL);
+  // Convert from second (1.0) and microsecond (1e-6).
+  return (static_cast<double>(time.tv_sec) +
+          static_cast<double>(time.tv_usec) * 1.0e-6);
+
+#elif defined(WIN32)
+  struct _timeb time;
+  _ftime(&time);
+  // Convert from second (1.0) and milliseconds (1e-3).
+  return (static_cast<double>(time.time) +
+          static_cast<double>(time.millitm) * 1.0e-3);
+#endif
+}
+
+double Timing::TimerNow() {
+#if defined(OSX)
+  // No clock_gettime on OSX.
+  clock_serv_t clock;
+  mach_timespec_t time;
+  host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clock);
+  clock_get_time(clock, &time);
+  return (static_cast<double>(time.tv_sec) +
+          static_cast<double>(time.tv_nsec) * 1.0e-9);
+#elif defined(POSIX)
+  struct timespec time;
+  clock_gettime(CLOCK_MONOTONIC, &time);
+  // Convert from second (1.0) and nanosecond (1e-9).
+  return (static_cast<double>(time.tv_sec) +
+          static_cast<double>(time.tv_nsec) * 1.0e-9);
+
+#elif defined(WIN32)
+  LARGE_INTEGER count;
+  QueryPerformanceCounter(&count);
+  return (static_cast<double>(count.QuadPart) /
+          static_cast<double>(tick_hz_.QuadPart));
+#endif
+}
+
+double Timing::BusyWait(double period) {
+  double start_time = TimerNow();
+  while (TimerNow() - start_time < period) {
+  }
+  return TimerNow() - start_time;
+}
+
+double Timing::IdleWait(double period) {
+  double start_time = TimerNow();
+
+#if defined(POSIX)
+  double sec_int, sec_frac = modf(period, &sec_int);
+  struct timespec ts;
+  ts.tv_sec = static_cast<time_t>(sec_int);
+  ts.tv_nsec = static_cast<long>(sec_frac * 1.0e9);  // NOLINT
+
+  // NOTE(liulk): for the NOLINT above, long is the appropriate POSIX
+  // type.
+
+  // POSIX nanosleep may be interrupted by signals.
+  while (nanosleep(&ts, &ts) == -1 && errno == EINTR) {
+  }
+
+#elif defined(WIN32)
+  if (timer_handle_ != NULL) {
+    LARGE_INTEGER due_time;
+
+    // Negative indicates relative time.  The unit is 100 nanoseconds.
+    due_time.QuadPart = -LONGLONG(period * 1.0e7);
+
+    SetWaitableTimer(timer_handle_, &due_time, 0, NULL, NULL, TRUE);
+    WaitForSingleObject(timer_handle_, INFINITE);
+  } else {
+    // Still attempts to sleep with lesser resolution.
+    // The unit is in milliseconds.
+    Sleep(DWORD(period * 1.0e3));
+  }
+#endif
+
+  return TimerNow() - start_time;
+}
diff --git a/talk/base/timing.h b/talk/base/timing.h
new file mode 100644
index 0000000..f94daf0
--- /dev/null
+++ b/talk/base/timing.h
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_MAGICFLUTE_TESTING_TIMING_H_
+#define TALK_MAGICFLUTE_TESTING_TIMING_H_
+
+#if defined(WIN32)
+#include "talk/base/win32.h"
+#endif
+
+class Timing {
+ public:
+  Timing();
+  virtual ~Timing();
+
+  // WallTimeNow() returns the current wall-clock time in seconds,
+  // within 10 milliseconds resolution.
+  double WallTimeNow();
+
+  // TimerNow() is like WallTimeNow(), but is monotonically
+  // increasing.  It returns seconds in resolution of 10 microseconds
+  // or better.  Although timer and wall-clock time have the same
+  // timing unit, they do not necessarily correlate because wall-clock
+  // time may be adjusted backwards, hence not monotonic.
+  double TimerNow();
+
+  // BusyWait() exhausts CPU as long as the time elapsed is less than
+  // the specified interval in seconds.  Returns the actual waiting
+  // time based on TimerNow() measurement.
+  double BusyWait(double period);
+
+  // IdleWait() relinquishes control of CPU for specified period in
+  // seconds.  It uses highest resolution sleep mechanism as possible,
+  // but does not otherwise guarantee the accuracy.  Returns the
+  // actual waiting time based on TimerNow() measurement.
+  //
+  // This function is not re-entrant for an object.  Create a fresh
+  // Timing object for each thread.
+  double IdleWait(double period);
+
+ private:
+#if defined(WIN32)
+  LARGE_INTEGER tick_hz_;
+  HANDLE timer_handle_;
+#endif
+};
+
+#endif  // TALK_MAGICFLUTE_TESTING_TIMING_H_
diff --git a/talk/base/transformadapter.cc b/talk/base/transformadapter.cc
new file mode 100644
index 0000000..53a55a8
--- /dev/null
+++ b/talk/base/transformadapter.cc
@@ -0,0 +1,202 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/transformadapter.h"
+
+#include <cstring>
+
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+TransformAdapter::TransformAdapter(StreamInterface * stream,
+                                   TransformInterface * transform,
+                                   bool direction_read)
+    : StreamAdapterInterface(stream), transform_(transform),
+      direction_read_(direction_read), state_(ST_PROCESSING), len_(0) {
+}
+
+TransformAdapter::~TransformAdapter() {
+  TransformAdapter::Close();
+  delete transform_;
+}
+
+StreamResult
+TransformAdapter::Read(void * buffer, size_t buffer_len,
+                       size_t * read, int * error) {
+  if (!direction_read_)
+    return SR_EOS;
+
+  while (state_ != ST_ERROR) {
+    if (state_ == ST_COMPLETE)
+      return SR_EOS;
+
+    // Buffer more data
+    if ((state_ == ST_PROCESSING) && (len_ < sizeof(buffer_))) {
+      size_t subread;
+      StreamResult result = StreamAdapterInterface::Read(
+                              buffer_ + len_,
+                              sizeof(buffer_) - len_,
+                              &subread,
+                              &error_);
+      if (result == SR_BLOCK) {
+        return SR_BLOCK;
+      } else if (result == SR_ERROR) {
+        state_ = ST_ERROR;
+        break;
+      } else if (result == SR_EOS) {
+        state_ = ST_FLUSHING;
+      } else {
+        len_ += subread;
+      }
+    }
+
+    // Process buffered data
+    size_t in_len = len_;
+    size_t out_len = buffer_len;
+    StreamResult result = transform_->Transform(buffer_, &in_len,
+                                                buffer, &out_len,
+                                                (state_ == ST_FLUSHING));
+    ASSERT(result != SR_BLOCK);
+    if (result == SR_EOS) {
+      // Note: Don't signal SR_EOS this iteration, unless out_len is zero
+      state_ = ST_COMPLETE;
+    } else if (result == SR_ERROR) {
+      state_ = ST_ERROR;
+      error_ = -1; // TODO: propagate error
+      break;
+    } else if ((out_len == 0) && (state_ == ST_FLUSHING)) {
+      // If there is no output AND no more input, then something is wrong
+      state_ = ST_ERROR;
+      error_ = -1; // TODO: better error code?
+      break;
+    }
+
+    len_ -= in_len;
+    if (len_ > 0)
+      memmove(buffer_, buffer_ + in_len, len_);
+
+    if (out_len == 0)
+      continue;
+
+    if (read)
+      *read = out_len;
+    return SR_SUCCESS;
+  }
+
+  if (error)
+    *error = error_;
+  return SR_ERROR;
+}
+
+StreamResult
+TransformAdapter::Write(const void * data, size_t data_len,
+                        size_t * written, int * error) {
+  if (direction_read_)
+    return SR_EOS;
+
+  size_t bytes_written = 0;
+  while (state_ != ST_ERROR) {
+    if (state_ == ST_COMPLETE)
+      return SR_EOS;
+
+    if (len_ < sizeof(buffer_)) {
+      // Process buffered data
+      size_t in_len = data_len;
+      size_t out_len = sizeof(buffer_) - len_;
+      StreamResult result = transform_->Transform(data, &in_len,
+                                                  buffer_ + len_, &out_len,
+                                                  (state_ == ST_FLUSHING));
+
+      ASSERT(result != SR_BLOCK);
+      if (result == SR_EOS) {
+        // Note: Don't signal SR_EOS this iteration, unless no data written
+        state_ = ST_COMPLETE;
+      } else if (result == SR_ERROR) {
+        ASSERT(false); // When this happens, think about what should be done
+        state_ = ST_ERROR;
+        error_ = -1; // TODO: propagate error
+        break;
+      }
+
+      len_ = out_len;
+      bytes_written = in_len;
+    }
+
+    size_t pos = 0;
+    while (pos < len_) {
+      size_t subwritten;
+      StreamResult result = StreamAdapterInterface::Write(buffer_ + pos,
+                                                          len_ - pos,
+                                                          &subwritten,
+                                                          &error_);
+      if (result == SR_BLOCK) {
+        ASSERT(false); // TODO: we should handle this
+        return SR_BLOCK;
+      } else if (result == SR_ERROR) {
+        state_ = ST_ERROR;
+        break;
+      } else if (result == SR_EOS) {
+        state_ = ST_COMPLETE;
+        break;
+      }
+
+      pos += subwritten;
+    }
+
+    len_ -= pos;
+    if (len_ > 0)
+      memmove(buffer_, buffer_ + pos, len_);
+
+    if (bytes_written == 0)
+      continue;
+
+    if (written)
+      *written = bytes_written;
+    return SR_SUCCESS;
+  }
+
+  if (error)
+    *error = error_;
+  return SR_ERROR;
+}
+
+void
+TransformAdapter::Close() {
+  if (!direction_read_ && (state_ == ST_PROCESSING)) {
+    state_ = ST_FLUSHING;
+    do {
+      Write(0, 0, NULL, NULL);
+    } while (state_ == ST_FLUSHING);
+  }
+  state_ = ST_COMPLETE;
+  StreamAdapterInterface::Close();
+}
+
+} // namespace talk_base
diff --git a/talk/base/transformadapter.h b/talk/base/transformadapter.h
new file mode 100644
index 0000000..e96a13d
--- /dev/null
+++ b/talk/base/transformadapter.h
@@ -0,0 +1,97 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_TRANSFORMADAPTER_H__
+#define TALK_BASE_TRANSFORMADAPTER_H__
+
+#include "talk/base/stream.h"
+
+namespace talk_base {
+///////////////////////////////////////////////////////////////////////////////
+
+class TransformInterface {
+public:
+  virtual ~TransformInterface() { }
+
+  // Transform should convert the in_len bytes of input into the out_len-sized
+  // output buffer.  If flush is true, there will be no more data following
+  // input.
+  // After the transformation, in_len contains the number of bytes consumed, and
+  // out_len contains the number of bytes ready in output.
+  // Note: Transform should not return SR_BLOCK, as there is no asynchronous
+  // notification available.
+  virtual StreamResult Transform(const void * input, size_t * in_len,
+                                 void * output, size_t * out_len,
+                                 bool flush) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// TransformAdapter causes all data passed through to be transformed by the
+// supplied TransformInterface object, which may apply compression, encryption,
+// etc.
+
+class TransformAdapter : public StreamAdapterInterface {
+public:
+  // Note that the transformation is unidirectional, in the direction specified
+  // by the constructor.  Operations in the opposite direction result in SR_EOS.
+  TransformAdapter(StreamInterface * stream,
+                   TransformInterface * transform,
+                   bool direction_read);
+  virtual ~TransformAdapter();
+  
+  virtual StreamResult Read(void * buffer, size_t buffer_len,
+                            size_t * read, int * error);
+  virtual StreamResult Write(const void * data, size_t data_len,
+                             size_t * written, int * error);
+  virtual void Close();
+
+  // Apriori, we can't tell what the transformation does to the stream length.
+  virtual bool GetAvailable(size_t* size) const { return false; }
+  virtual bool ReserveSize(size_t size) { return true; }
+
+  // Transformations might not be restartable
+  virtual bool Rewind() { return false; }
+
+private:
+  enum State { ST_PROCESSING, ST_FLUSHING, ST_COMPLETE, ST_ERROR };
+  enum { BUFFER_SIZE = 1024 };
+
+  TransformInterface * transform_;
+  bool direction_read_;
+  State state_;
+  int error_;
+
+  char buffer_[BUFFER_SIZE];
+  size_t len_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_TRANSFORMADAPTER_H__
diff --git a/talk/base/unittest_main.cc b/talk/base/unittest_main.cc
index 56fbc54..bca3671 100644
--- a/talk/base/unittest_main.cc
+++ b/talk/base/unittest_main.cc
@@ -69,7 +69,6 @@
   return path;
 }
 
-
 int main(int argc, char** argv) {
   testing::InitGoogleTest(&argc, argv);
   FlagList::SetFlagsFromCommandLine(&argc, argv, false);
diff --git a/talk/base/unixfilesystem.cc b/talk/base/unixfilesystem.cc
index 096c511..02d0a73 100644
--- a/talk/base/unixfilesystem.cc
+++ b/talk/base/unixfilesystem.cc
@@ -483,9 +483,9 @@
     existing_path.SetFolder(existing_path.parent_folder());
   }
 #ifdef ANDROID
-  struct statfs fs;
-  memset(&fs, 0, sizeof(fs));
-  if (0 != statfs(existing_path.pathname().c_str(), &fs))
+  struct statfs vfs;
+  memset(&vfs, 0, sizeof(vfs));
+  if (0 != statfs(existing_path.pathname().c_str(), &vfs))
     return false;
 #else
   struct statvfs vfs;
@@ -493,12 +493,10 @@
   if (0 != statvfs(existing_path.pathname().c_str(), &vfs))
     return false;
 #endif  // ANDROID
-#ifdef LINUX
+#if defined(LINUX) || defined(ANDROID)
   *freebytes = static_cast<int64>(vfs.f_bsize) * vfs.f_bavail;
 #elif defined(OSX)
   *freebytes = static_cast<int64>(vfs.f_frsize) * vfs.f_bavail;
-#elif defined(ANDROID)
-  *freebytes = static_cast<int64>(fs.f_bsize) * fs.f_bavail;
 #endif
 
   return true;
diff --git a/talk/base/versionparsing.cc b/talk/base/versionparsing.cc
new file mode 100644
index 0000000..03f3dec
--- /dev/null
+++ b/talk/base/versionparsing.cc
@@ -0,0 +1,74 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/versionparsing.h"
+
+#include <cstdlib>
+
+namespace talk_base {
+
+bool ParseVersionString(const std::string& version_str,
+                        int num_expected_segments,
+                        int version[]) {
+  size_t pos = 0;
+  for (int i = 0;;) {
+    size_t dot_pos = version_str.find('.', pos);
+    size_t n;
+    if (dot_pos == std::string::npos) {
+      // npos here is a special value meaning "to the end of the string"
+      n = std::string::npos;
+    } else {
+      n = dot_pos - pos;
+    }
+
+    version[i] = atoi(version_str.substr(pos, n).c_str());
+
+    if (++i >= num_expected_segments) break;
+
+    if (dot_pos == std::string::npos) {
+      // Previous segment was not terminated by a dot, but there's supposed to
+      // be more segments, so that's an error.
+      return false;
+    }
+    pos = dot_pos + 1;
+  }
+  return true;
+}
+
+int CompareVersions(const int version1[],
+                    const int version2[],
+                    int num_segments) {
+  for (int i = 0; i < num_segments; ++i) {
+    int diff = version1[i] - version2[i];
+    if (diff != 0) {
+      return diff;
+    }
+  }
+  return 0;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/versionparsing.h b/talk/base/versionparsing.h
new file mode 100644
index 0000000..c66ad25
--- /dev/null
+++ b/talk/base/versionparsing.h
@@ -0,0 +1,52 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_VERSIONPARSING_H_
+#define TALK_BASE_VERSIONPARSING_H_
+
+#include <string>
+
+namespace talk_base {
+
+// Parses a version string into an array. "num_expected_segments" must be the
+// number of numerical segments that the version is expected to have (e.g.,
+// "1.1.2.0" has 4). "version" must be an array of that length to hold the
+// parsed numbers.
+// Returns "true" iff successful.
+bool ParseVersionString(const std::string& version_str,
+                        int num_expected_segments,
+                        int version[]);
+
+// Computes the lexicographical order of two versions. The return value
+// indicates the order in the standard way (e.g., see strcmp()).
+int CompareVersions(const int version1[],
+                    const int version2[],
+                    int num_segments);
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_VERSIONPARSING_H_
diff --git a/talk/base/versionparsing_unittest.cc b/talk/base/versionparsing_unittest.cc
new file mode 100644
index 0000000..b083265
--- /dev/null
+++ b/talk/base/versionparsing_unittest.cc
@@ -0,0 +1,91 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/versionparsing.h"
+
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+static const int kExampleSegments = 4;
+
+typedef int ExampleVersion[kExampleSegments];
+
+TEST(VersionParsing, TestGoodParse) {
+  ExampleVersion ver;
+  std::string str1("1.1.2.0");
+  static const ExampleVersion expect1 = {1, 1, 2, 0};
+  EXPECT_TRUE(ParseVersionString(str1, kExampleSegments, ver));
+  EXPECT_EQ(0, CompareVersions(ver, expect1, kExampleSegments));
+  std::string str2("2.0.0.1");
+  static const ExampleVersion expect2 = {2, 0, 0, 1};
+  EXPECT_TRUE(ParseVersionString(str2, kExampleSegments, ver));
+  EXPECT_EQ(0, CompareVersions(ver, expect2, kExampleSegments));
+}
+
+TEST(VersionParsing, TestBadParse) {
+  ExampleVersion ver;
+  std::string str1("1.1.2");
+  EXPECT_FALSE(ParseVersionString(str1, kExampleSegments, ver));
+  std::string str2("");
+  EXPECT_FALSE(ParseVersionString(str2, kExampleSegments, ver));
+  std::string str3("garbarge");
+  EXPECT_FALSE(ParseVersionString(str3, kExampleSegments, ver));
+}
+
+TEST(VersionParsing, TestCompare) {
+  static const ExampleVersion ver1 = {1, 0, 21, 0};
+  static const ExampleVersion ver2 = {1, 1, 2, 0};
+  static const ExampleVersion ver3 = {1, 1, 3, 0};
+  static const ExampleVersion ver4 = {1, 1, 3, 9861};
+
+  // Test that every combination of comparisons has the expected outcome.
+  EXPECT_EQ(0, CompareVersions(ver1, ver1, kExampleSegments));
+  EXPECT_EQ(0, CompareVersions(ver2, ver2, kExampleSegments));
+  EXPECT_EQ(0, CompareVersions(ver3, ver3, kExampleSegments));
+  EXPECT_EQ(0, CompareVersions(ver4, ver4, kExampleSegments));
+
+  EXPECT_GT(0, CompareVersions(ver1, ver2, kExampleSegments));
+  EXPECT_LT(0, CompareVersions(ver2, ver1, kExampleSegments));
+
+  EXPECT_GT(0, CompareVersions(ver1, ver3, kExampleSegments));
+  EXPECT_LT(0, CompareVersions(ver3, ver1, kExampleSegments));
+
+  EXPECT_GT(0, CompareVersions(ver1, ver4, kExampleSegments));
+  EXPECT_LT(0, CompareVersions(ver4, ver1, kExampleSegments));
+
+  EXPECT_GT(0, CompareVersions(ver2, ver3, kExampleSegments));
+  EXPECT_LT(0, CompareVersions(ver3, ver2, kExampleSegments));
+
+  EXPECT_GT(0, CompareVersions(ver2, ver4, kExampleSegments));
+  EXPECT_LT(0, CompareVersions(ver4, ver2, kExampleSegments));
+
+  EXPECT_GT(0, CompareVersions(ver3, ver4, kExampleSegments));
+  EXPECT_LT(0, CompareVersions(ver4, ver3, kExampleSegments));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/virtualsocket_unittest.cc b/talk/base/virtualsocket_unittest.cc
new file mode 100644
index 0000000..93fb5ef
--- /dev/null
+++ b/talk/base/virtualsocket_unittest.cc
@@ -0,0 +1,1009 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <time.h>
+#ifdef POSIX
+#include <netinet/in.h>
+#endif
+#include <cmath>
+
+#include "talk/base/logging.h"
+#include "talk/base/gunit.h"
+#include "talk/base/testclient.h"
+#include "talk/base/testutils.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/base/virtualsocketserver.h"
+
+using namespace talk_base;
+
+// Sends at a constant rate but with random packet sizes.
+struct Sender : public MessageHandler {
+  Sender(Thread* th, AsyncSocket* s, uint32 rt)
+      : thread(th), socket(new AsyncUDPSocket(s)),
+        done(false), rate(rt), count(0) {
+    last_send = Time();
+    thread->PostDelayed(NextDelay(), this, 1);
+  }
+
+  uint32 NextDelay() {
+    uint32 size = (rand() % 4096) + 1;
+    return 1000 * size / rate;
+  }
+
+  void OnMessage(Message* pmsg) {
+    ASSERT_EQ(1u, pmsg->message_id);
+
+    if (done)
+      return;
+
+    uint32 cur_time = Time();
+    uint32 delay = cur_time - last_send;
+    uint32 size = rate * delay / 1000;
+    size = std::min<uint32>(size, 4096);
+    size = std::max<uint32>(size, sizeof(uint32));
+
+    count += size;
+    memcpy(dummy, &cur_time, sizeof(cur_time));
+    socket->Send(dummy, size);
+
+    last_send = cur_time;
+    thread->PostDelayed(NextDelay(), this, 1);
+  }
+
+  Thread* thread;
+  scoped_ptr<AsyncUDPSocket> socket;
+  bool done;
+  uint32 rate;  // bytes per second
+  uint32 count;
+  uint32 last_send;
+  char dummy[4096];
+};
+
+struct Receiver : public MessageHandler, public sigslot::has_slots<> {
+  Receiver(Thread* th, AsyncSocket* s, uint32 bw)
+      : thread(th), socket(new AsyncUDPSocket(s)), bandwidth(bw), done(false),
+        count(0), sec_count(0), sum(0), sum_sq(0), samples(0) {
+    socket->SignalReadPacket.connect(this, &Receiver::OnReadPacket);
+    thread->PostDelayed(1000, this, 1);
+  }
+
+  ~Receiver() {
+    thread->Clear(this);
+  }
+
+  void OnReadPacket(AsyncPacketSocket* s, const char* data, size_t size,
+                    const SocketAddress& remote_addr) {
+    ASSERT_EQ(socket.get(), s);
+    ASSERT_GE(size, 4U);
+
+    count += size;
+    sec_count += size;
+
+    uint32 send_time = *reinterpret_cast<const uint32*>(data);
+    uint32 recv_time = Time();
+    uint32 delay = recv_time - send_time;
+    sum += delay;
+    sum_sq += delay * delay;
+    samples += 1;
+  }
+
+  void OnMessage(Message* pmsg) {
+    ASSERT_EQ(1u, pmsg->message_id);
+
+    if (done)
+      return;
+
+    // It is always possible for us to receive more than expected because
+    // packets can be further delayed in delivery.
+    if (bandwidth > 0)
+      ASSERT_TRUE(sec_count <= 5 * bandwidth / 4);
+    sec_count = 0;
+    thread->PostDelayed(1000, this, 1);
+  }
+
+  Thread* thread;
+  scoped_ptr<AsyncUDPSocket> socket;
+  uint32 bandwidth;
+  bool done;
+  uint32 count;
+  uint32 sec_count;
+  double sum;
+  double sum_sq;
+  uint32 samples;
+};
+
+class VirtualSocketServerTest : public testing::Test {
+ public:
+  VirtualSocketServerTest() : ss_(new VirtualSocketServer(NULL)),
+                              kIPv4AnyAddress(IPAddress(INADDR_ANY), 0),
+                              kIPv6AnyAddress(IPAddress(in6addr_any), 0) {
+  }
+
+  void CheckAddressIncrementalization(const SocketAddress& post,
+                                      const SocketAddress& pre) {
+    EXPECT_EQ(post.port(), pre.port() + 1);
+    IPAddress post_ip = post.ipaddr();
+    IPAddress pre_ip = pre.ipaddr();
+    EXPECT_EQ(pre_ip.family(), post_ip.family());
+    if (post_ip.family() == AF_INET) {
+      in_addr pre_ipv4 = pre_ip.ipv4_address();
+      in_addr post_ipv4 = post_ip.ipv4_address();
+      int difference = ntohl(post_ipv4.s_addr) - ntohl(pre_ipv4.s_addr);
+      EXPECT_EQ(1, difference);
+    } else if (post_ip.family() == AF_INET6) {
+      in6_addr post_ip6 = post_ip.ipv6_address();
+      in6_addr pre_ip6 = pre_ip.ipv6_address();
+      uint32* post_as_ints = reinterpret_cast<uint32*>(&post_ip6.s6_addr);
+      uint32* pre_as_ints = reinterpret_cast<uint32*>(&pre_ip6.s6_addr);
+      EXPECT_EQ(post_as_ints[3], pre_as_ints[3] + 1);
+    }
+  }
+
+  void BasicTest(const SocketAddress& initial_addr) {
+    AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    socket->Bind(initial_addr);
+    SocketAddress server_addr = socket->GetLocalAddress();
+    // Make sure VSS didn't switch families on us.
+    EXPECT_EQ(server_addr.ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    TestClient* client1 = new TestClient(new AsyncUDPSocket(socket));
+    AsyncSocket* socket2 = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2));
+
+    SocketAddress client2_addr;
+    EXPECT_EQ(3, client2->SendTo("foo", 3, server_addr));
+    EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &client2_addr));
+
+    SocketAddress client1_addr;
+    EXPECT_EQ(6, client1->SendTo("bizbaz", 6, client2_addr));
+    EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &client1_addr));
+    EXPECT_EQ(client1_addr, server_addr);
+
+    for (int i = 0; i < 10; i++) {
+      client2 = new TestClient(AsyncUDPSocket::Create(ss_, SocketAddress()));
+
+      SocketAddress next_client2_addr;
+      EXPECT_EQ(3, client2->SendTo("foo", 3, server_addr));
+      EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &next_client2_addr));
+      CheckAddressIncrementalization(next_client2_addr, client2_addr);
+      // EXPECT_EQ(next_client2_addr.port(), client2_addr.port() + 1);
+
+      SocketAddress server_addr2;
+      EXPECT_EQ(6, client1->SendTo("bizbaz", 6, next_client2_addr));
+      EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &server_addr2));
+      EXPECT_EQ(server_addr2, server_addr);
+
+      client2_addr = next_client2_addr;
+    }
+  }
+
+  // initial_addr should be made from either INADDR_ANY or in6addr_any.
+  void ConnectTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    SocketAddress accept_addr;
+    const SocketAddress kEmptyAddr;
+
+    // Create client
+    AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(client);
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_EQ(client->GetLocalAddress(), kEmptyAddr);
+
+    // Create server
+    AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+    EXPECT_NE(0, server->Listen(5));  // Bind required
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    EXPECT_EQ(0, server->Listen(5));
+    EXPECT_EQ(server->GetState(), AsyncSocket::CS_CONNECTING);
+
+    // No pending server connections
+    EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+    EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+    EXPECT_EQ(accept_addr, kEmptyAddr);
+
+    // Attempt connect to listening socket
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+    EXPECT_NE(client->GetLocalAddress(), kEmptyAddr);  // Implicit Bind
+    EXPECT_NE(client->GetLocalAddress(), server->GetLocalAddress());
+
+    // Client is connecting
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
+    EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+    EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Client still connecting
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
+    EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+    EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+
+    // Server has pending connection
+    EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+    Socket* accepted = server->Accept(&accept_addr);
+    EXPECT_TRUE(NULL != accepted);
+    EXPECT_NE(accept_addr, kEmptyAddr);
+    EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr);
+
+    EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED);
+    EXPECT_EQ(accepted->GetLocalAddress(), server->GetLocalAddress());
+    EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Client has connected
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTED);
+    EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN));
+    EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+    EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+    EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+  }
+
+  void ConnectToNonListenerTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    SocketAddress accept_addr;
+    const SocketAddress kEmptyAddr;
+
+    // Create client
+    AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(client);
+
+    // Create server
+    AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    // Attempt connect to non-listening socket
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // No pending server connections
+    EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+    EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+    EXPECT_EQ(accept_addr, kEmptyAddr);
+
+    // Connection failed
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+    EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
+    EXPECT_EQ(client->GetRemoteAddress(), kEmptyAddr);
+  }
+
+  void CloseDuringConnectTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    SocketAddress accept_addr;
+    const SocketAddress kEmptyAddr;
+
+    // Create client and server
+    AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(client);
+    AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+
+    // Initiate connect
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, server->Listen(5));
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+    // Server close before socket enters accept queue
+    EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+    server->Close();
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Result: connection failed
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
+
+    // New server
+    delete server;
+    server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+
+    // Initiate connect
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, server->Listen(5));
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Server close while socket is in accept queue
+    EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+    server->Close();
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Result: connection failed
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
+
+    // New server
+    delete server;
+    server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+
+    // Initiate connect
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, server->Listen(5));
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Server accepts connection
+    EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+    AsyncSocket* accepted = server->Accept(&accept_addr);
+    ASSERT_TRUE(NULL != accepted);
+    sink.Monitor(accepted);
+
+    // Client closes before connection complets
+    EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED);
+
+    // Connected message has not been processed yet.
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
+    client->Close();
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Result: accepted socket closes
+    EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_TRUE(sink.Check(accepted, testing::SSE_CLOSE));
+    EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+  }
+
+  void CloseTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    const SocketAddress kEmptyAddr;
+
+    // Create clients
+    AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(a);
+    a->Bind(initial_addr);
+    EXPECT_EQ(a->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+
+    AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(b);
+    b->Bind(initial_addr);
+    EXPECT_EQ(b->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
+    EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    EXPECT_TRUE(sink.Check(a, testing::SSE_OPEN));
+    EXPECT_EQ(a->GetState(), AsyncSocket::CS_CONNECTED);
+    EXPECT_EQ(a->GetRemoteAddress(), b->GetLocalAddress());
+
+    EXPECT_TRUE(sink.Check(b, testing::SSE_OPEN));
+    EXPECT_EQ(b->GetState(), AsyncSocket::CS_CONNECTED);
+    EXPECT_EQ(b->GetRemoteAddress(), a->GetLocalAddress());
+
+    EXPECT_EQ(1, a->Send("a", 1));
+    b->Close();
+    EXPECT_EQ(1, a->Send("b", 1));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    char buffer[10];
+    EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
+    EXPECT_EQ(-1, b->Recv(buffer, 10));
+
+    EXPECT_TRUE(sink.Check(a, testing::SSE_CLOSE));
+    EXPECT_EQ(a->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_EQ(a->GetRemoteAddress(), kEmptyAddr);
+
+    EXPECT_FALSE(sink.Check(b, testing::SSE_CLOSE));  // No signal for Closer
+    EXPECT_EQ(b->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_EQ(b->GetRemoteAddress(), kEmptyAddr);
+  }
+
+  void TcpSendTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    const SocketAddress kEmptyAddr;
+
+    // Connect two sockets
+    AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(a);
+    a->Bind(initial_addr);
+    EXPECT_EQ(a->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(b);
+    b->Bind(initial_addr);
+    EXPECT_EQ(b->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
+    EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    const size_t kBufferSize = 2000;
+    ss_->set_send_buffer_capacity(kBufferSize);
+    ss_->set_recv_buffer_capacity(kBufferSize);
+
+    const size_t kDataSize = 5000;
+    char send_buffer[kDataSize], recv_buffer[kDataSize];
+    for (size_t i = 0; i < kDataSize; ++i) send_buffer[i] = i;
+    memset(recv_buffer, 0, sizeof(recv_buffer));
+    size_t send_pos = 0, recv_pos = 0;
+
+    // Can't send more than send buffer in one write
+    int result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+    EXPECT_EQ(static_cast<int>(kBufferSize), result);
+    send_pos += result;
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE));
+    EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+    // Receive buffer is already filled, fill send buffer again
+    result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+    EXPECT_EQ(static_cast<int>(kBufferSize), result);
+    send_pos += result;
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE));
+    EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
+
+    // No more room in send or receive buffer
+    result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+    EXPECT_EQ(-1, result);
+    EXPECT_TRUE(a->IsBlocking());
+
+    // Read a subset of the data
+    result = b->Recv(recv_buffer + recv_pos, 500);
+    EXPECT_EQ(500, result);
+    recv_pos += result;
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_TRUE(sink.Check(a, testing::SSE_WRITE));
+    EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+    // Room for more on the sending side
+    result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+    EXPECT_EQ(500, result);
+    send_pos += result;
+
+    // Empty the recv buffer
+    while (true) {
+      result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
+      if (result < 0) {
+        EXPECT_EQ(-1, result);
+        EXPECT_TRUE(b->IsBlocking());
+        break;
+      }
+      recv_pos += result;
+    }
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+    // Continue to empty the recv buffer
+    while (true) {
+      result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
+      if (result < 0) {
+        EXPECT_EQ(-1, result);
+        EXPECT_TRUE(b->IsBlocking());
+        break;
+      }
+      recv_pos += result;
+    }
+
+    // Send last of the data
+    result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+    EXPECT_EQ(500, result);
+    send_pos += result;
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+    // Receive the last of the data
+    while (true) {
+      result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
+      if (result < 0) {
+        EXPECT_EQ(-1, result);
+        EXPECT_TRUE(b->IsBlocking());
+        break;
+      }
+      recv_pos += result;
+    }
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
+
+    // The received data matches the sent data
+    EXPECT_EQ(kDataSize, send_pos);
+    EXPECT_EQ(kDataSize, recv_pos);
+    EXPECT_EQ(0, memcmp(recv_buffer, send_buffer, kDataSize));
+  }
+
+  void TcpSendsPacketsInOrderTest(const SocketAddress& initial_addr) {
+    const SocketAddress kEmptyAddr;
+
+    // Connect two sockets
+    AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
+    AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
+    a->Bind(initial_addr);
+    EXPECT_EQ(a->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    b->Bind(initial_addr);
+    EXPECT_EQ(b->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
+    EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
+    ss_->ProcessMessagesUntilIdle();
+
+    // First, deliver all packets in 0 ms.
+    char buffer[2] = { 0, 0 };
+    const size_t cNumPackets = 10;
+    for (size_t i = 0; i < cNumPackets; ++i) {
+      buffer[0] = '0' + i;
+      EXPECT_EQ(1, a->Send(buffer, 1));
+    }
+
+    ss_->ProcessMessagesUntilIdle();
+
+    for (size_t i = 0; i < cNumPackets; ++i) {
+      EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer)));
+      EXPECT_EQ(static_cast<char>('0' + i), buffer[0]);
+    }
+
+    // Next, deliver packets at random intervals
+    const uint32 mean = 50;
+    const uint32 stddev = 50;
+
+    ss_->set_delay_mean(mean);
+    ss_->set_delay_stddev(stddev);
+    ss_->UpdateDelayDistribution();
+
+    for (size_t i = 0; i < cNumPackets; ++i) {
+      buffer[0] = 'A' + i;
+      EXPECT_EQ(1, a->Send(buffer, 1));
+    }
+
+    ss_->ProcessMessagesUntilIdle();
+
+    for (size_t i = 0; i < cNumPackets; ++i) {
+      EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer)));
+      EXPECT_EQ(static_cast<char>('A' + i), buffer[0]);
+    }
+  }
+
+  void BandwidthTest(const SocketAddress& initial_addr) {
+    AsyncSocket* send_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    AsyncSocket* recv_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    ASSERT_EQ(0, send_socket->Bind(initial_addr));
+    ASSERT_EQ(0, recv_socket->Bind(initial_addr));
+    EXPECT_EQ(send_socket->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    EXPECT_EQ(recv_socket->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress()));
+
+    uint32 bandwidth = 64 * 1024;
+    ss_->set_bandwidth(bandwidth);
+
+    Thread* pthMain = Thread::Current();
+    Sender sender(pthMain, send_socket, 80 * 1024);
+    Receiver receiver(pthMain, recv_socket, bandwidth);
+
+    pthMain->ProcessMessages(5000);
+    sender.done = true;
+    pthMain->ProcessMessages(5000);
+
+    ASSERT_TRUE(receiver.count >= 5 * 3 * bandwidth / 4);
+    ASSERT_TRUE(receiver.count <= 6 * bandwidth);  // queue could drain for 1s
+
+    ss_->set_bandwidth(0);
+  }
+
+  void DelayTest(const SocketAddress& initial_addr) {
+    time_t seed = ::time(NULL);
+    LOG(LS_VERBOSE) << "seed = " << seed;
+    srand(seed);
+
+    const uint32 mean = 2000;
+    const uint32 stddev = 500;
+
+    ss_->set_delay_mean(mean);
+    ss_->set_delay_stddev(stddev);
+    ss_->UpdateDelayDistribution();
+
+    AsyncSocket* send_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    AsyncSocket* recv_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    ASSERT_EQ(0, send_socket->Bind(initial_addr));
+    ASSERT_EQ(0, recv_socket->Bind(initial_addr));
+    EXPECT_EQ(send_socket->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    EXPECT_EQ(recv_socket->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress()));
+
+    Thread* pthMain = Thread::Current();
+    // Avg packet size is 2K, so at 200KB/s for 10s, we should see about
+    // 1000 packets, which is necessary to get a good distribution.
+    Sender sender(pthMain, send_socket, 100 * 2 * 1024);
+    Receiver receiver(pthMain, recv_socket, 0);
+
+    pthMain->ProcessMessages(10000);
+    sender.done = receiver.done = true;
+    ss_->ProcessMessagesUntilIdle();
+
+    const double sample_mean = receiver.sum / receiver.samples;
+    double num =
+        receiver.samples * receiver.sum_sq - receiver.sum * receiver.sum;
+    double den = receiver.samples * (receiver.samples - 1);
+    const double sample_stddev = std::sqrt(num / den);
+    LOG(LS_VERBOSE) << "mean=" << sample_mean << " stddev=" << sample_stddev;
+
+    EXPECT_LE(500u, receiver.samples);
+    // We initially used a 0.1 fudge factor, but on the build machine, we
+    // have seen the value differ by as much as 0.13.
+    EXPECT_NEAR(mean, sample_mean, 0.15 * mean);
+    EXPECT_NEAR(stddev, sample_stddev, 0.15 * stddev);
+
+    ss_->set_delay_mean(0);
+    ss_->set_delay_stddev(0);
+    ss_->UpdateDelayDistribution();
+  }
+
+  // Test cross-family communication between a client bound to client_addr and a
+  // server bound to server_addr. shouldSucceed indicates if communication is
+  // expected to work or not.
+  void CrossFamilyConnectionTest(const SocketAddress& client_addr,
+                                 const SocketAddress& server_addr,
+                                 bool shouldSucceed) {
+    testing::StreamSink sink;
+    SocketAddress accept_address;
+    const SocketAddress kEmptyAddr;
+
+    // Client gets a IPv4 address
+    AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(client);
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_EQ(client->GetLocalAddress(), kEmptyAddr);
+    client->Bind(client_addr);
+
+    // Server gets a non-mapped non-any IPv6 address.
+    // IPv4 sockets should not be able to connect to this.
+    AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+    server->Bind(server_addr);
+    server->Listen(5);
+
+    if (shouldSucceed) {
+      EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+      ss_->ProcessMessagesUntilIdle();
+      EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+      Socket* accepted = server->Accept(&accept_address);
+      EXPECT_TRUE(NULL != accepted);
+      EXPECT_NE(kEmptyAddr, accept_address);
+      ss_->ProcessMessagesUntilIdle();
+      EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN));
+      EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+    } else {
+      // Check that the connection failed.
+      EXPECT_EQ(-1, client->Connect(server->GetLocalAddress()));
+      ss_->ProcessMessagesUntilIdle();
+
+      EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+      EXPECT_TRUE(NULL == server->Accept(&accept_address));
+      EXPECT_EQ(accept_address, kEmptyAddr);
+      EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+      EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+      EXPECT_EQ(client->GetRemoteAddress(), kEmptyAddr);
+    }
+  }
+
+  // Test cross-family datagram sending between a client bound to client_addr
+  // and a server bound to server_addr. shouldSucceed indicates if sending is
+  // expected to succed or not.
+  void CrossFamilyDatagramTest(const SocketAddress& client_addr,
+                               const SocketAddress& server_addr,
+                               bool shouldSucceed) {
+    AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    socket->Bind(server_addr);
+    SocketAddress bound_server_addr = socket->GetLocalAddress();
+    TestClient* client1 = new TestClient(new AsyncUDPSocket(socket));
+
+    AsyncSocket* socket2 = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    socket2->Bind(client_addr);
+    TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2));
+    SocketAddress client2_addr;
+
+    if (shouldSucceed) {
+      EXPECT_EQ(3, client2->SendTo("foo", 3, bound_server_addr));
+      EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &client2_addr));
+      SocketAddress client1_addr;
+      EXPECT_EQ(6, client1->SendTo("bizbaz", 6, client2_addr));
+      EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &client1_addr));
+      EXPECT_EQ(client1_addr, bound_server_addr);
+    } else {
+      EXPECT_EQ(-1, client2->SendTo("foo", 3, bound_server_addr));
+      EXPECT_FALSE(client1->CheckNextPacket("foo", 3, 0));
+    }
+  }
+
+ protected:
+  virtual void SetUp() {
+    Thread::Current()->set_socketserver(ss_);
+  }
+  virtual void TearDown() {
+    Thread::Current()->set_socketserver(NULL);
+  }
+
+  VirtualSocketServer* ss_;
+  const SocketAddress kIPv4AnyAddress;
+  const SocketAddress kIPv6AnyAddress;
+};
+
+TEST_F(VirtualSocketServerTest, basic_v4) {
+  SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 5000);
+  BasicTest(ipv4_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, basic_v6) {
+  SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 5000);
+  BasicTest(ipv6_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, connect_v4) {
+  ConnectTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, connect_v6) {
+  ConnectTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, connect_to_non_listener_v4) {
+  ConnectToNonListenerTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, connect_to_non_listener_v6) {
+  ConnectToNonListenerTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, close_during_connect_v4) {
+  CloseDuringConnectTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, close_during_connect_v6) {
+  CloseDuringConnectTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, close_v4) {
+  CloseTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, close_v6) {
+  CloseTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, tcp_send_v4) {
+  TcpSendTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, tcp_send_v6) {
+  TcpSendTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder_v4) {
+  TcpSendsPacketsInOrderTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder_v6) {
+  TcpSendsPacketsInOrderTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, bandwidth_v4) {
+  SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 1000);
+  BandwidthTest(ipv4_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, bandwidth_v6) {
+  SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 1000);
+  BandwidthTest(ipv6_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, delay_v4) {
+  SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 1000);
+  DelayTest(ipv4_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, delay_v6) {
+  SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 1000);
+  DelayTest(ipv6_test_addr);
+}
+
+// Works, receiving socket sees 127.0.0.2.
+TEST_F(VirtualSocketServerTest, CanConnectFromMappedIPv6ToIPv4Any) {
+  CrossFamilyConnectionTest(SocketAddress("::ffff:127.0.0.2", 0),
+                            SocketAddress("0.0.0.0", 5000),
+                            true);
+}
+
+// Fails.
+TEST_F(VirtualSocketServerTest, CantConnectFromUnMappedIPv6ToIPv4Any) {
+  CrossFamilyConnectionTest(SocketAddress("::2", 0),
+                            SocketAddress("0.0.0.0", 5000),
+                            false);
+}
+
+// Fails.
+TEST_F(VirtualSocketServerTest, CantConnectFromUnMappedIPv6ToMappedIPv6) {
+  CrossFamilyConnectionTest(SocketAddress("::2", 0),
+                            SocketAddress("::ffff:127.0.0.1", 5000),
+                            false);
+}
+
+// Works. receiving socket sees ::ffff:127.0.0.2.
+TEST_F(VirtualSocketServerTest, CanConnectFromIPv4ToIPv6Any) {
+  CrossFamilyConnectionTest(SocketAddress("127.0.0.2", 0),
+                            SocketAddress("::", 5000),
+                            true);
+}
+
+// Fails.
+TEST_F(VirtualSocketServerTest, CantConnectFromIPv4ToUnMappedIPv6) {
+  CrossFamilyConnectionTest(SocketAddress("127.0.0.2", 0),
+                            SocketAddress("::1", 5000),
+                            false);
+}
+
+// Works. Receiving socket sees ::ffff:127.0.0.1.
+TEST_F(VirtualSocketServerTest, CanConnectFromIPv4ToMappedIPv6) {
+  CrossFamilyConnectionTest(SocketAddress("127.0.0.1", 0),
+                            SocketAddress("::ffff:127.0.0.2", 5000),
+                            true);
+}
+
+// Works, receiving socket sees a result from GetNextIP.
+TEST_F(VirtualSocketServerTest, CanConnectFromUnboundIPv6ToIPv4Any) {
+  CrossFamilyConnectionTest(SocketAddress("::", 0),
+                            SocketAddress("0.0.0.0", 5000),
+                            true);
+}
+
+// Works, receiving socket sees whatever GetNextIP gave the client.
+TEST_F(VirtualSocketServerTest, CanConnectFromUnboundIPv4ToIPv6Any) {
+  CrossFamilyConnectionTest(SocketAddress(),
+                            SocketAddress("::", 5000),
+                            true);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv4ToIPv6Any) {
+  CrossFamilyDatagramTest(SocketAddress(),
+                          SocketAddress("::", 5000),
+                          true);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromMappedIPv6ToIPv4Any) {
+  CrossFamilyDatagramTest(SocketAddress("::ffff:127.0.0.1", 0),
+                          SocketAddress("0.0.0.0", 5000),
+                          true);
+}
+
+TEST_F(VirtualSocketServerTest, CantSendDatagramFromUnMappedIPv6ToIPv4Any) {
+  CrossFamilyDatagramTest(SocketAddress("::2", 0),
+                          SocketAddress("0.0.0.0", 5000),
+                          false);
+}
+
+TEST_F(VirtualSocketServerTest, CantSendDatagramFromUnMappedIPv6ToMappedIPv6) {
+  CrossFamilyDatagramTest(SocketAddress("::2", 0),
+                          SocketAddress("::ffff:127.0.0.1", 5000),
+                          false);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromIPv4ToIPv6Any) {
+  CrossFamilyDatagramTest(SocketAddress("127.0.0.2", 0),
+                          SocketAddress("::", 5000),
+                          true);
+}
+
+TEST_F(VirtualSocketServerTest, CantSendDatagramFromIPv4ToUnMappedIPv6) {
+  CrossFamilyDatagramTest(SocketAddress("127.0.0.2", 0),
+                          SocketAddress("::1", 5000),
+                          false);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromIPv4ToMappedIPv6) {
+  CrossFamilyDatagramTest(SocketAddress("127.0.0.1", 0),
+                          SocketAddress("::ffff:127.0.0.2", 5000),
+                          true);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv6ToIPv4Any) {
+  CrossFamilyDatagramTest(SocketAddress("::", 0),
+                          SocketAddress("0.0.0.0", 5000),
+                          true);
+}
+
+TEST_F(VirtualSocketServerTest, CreatesStandardDistribution) {
+  const uint32 kTestMean[] = { 10, 100, 333, 1000 };
+  const double kTestDev[] = { 0.25, 0.1, 0.01 };
+  // TODO: The current code only works for 1000 data points or more.
+  const uint32 kTestSamples[] = { /*10, 100,*/ 1000 };
+  for (size_t midx = 0; midx < ARRAY_SIZE(kTestMean); ++midx) {
+    for (size_t didx = 0; didx < ARRAY_SIZE(kTestDev); ++didx) {
+      for (size_t sidx = 0; sidx < ARRAY_SIZE(kTestSamples); ++sidx) {
+        ASSERT_LT(0u, kTestSamples[sidx]);
+        const uint32 kStdDev =
+            static_cast<uint32>(kTestDev[didx] * kTestMean[midx]);
+        VirtualSocketServer::Function* f =
+            VirtualSocketServer::CreateDistribution(kTestMean[midx],
+                                                    kStdDev,
+                                                    kTestSamples[sidx]);
+        ASSERT_TRUE(NULL != f);
+        ASSERT_EQ(kTestSamples[sidx], f->size());
+        double sum = 0;
+        for (uint32 i = 0; i < f->size(); ++i) {
+          sum += (*f)[i].second;
+        }
+        const double mean = sum / f->size();
+        double sum_sq_dev = 0;
+        for (uint32 i = 0; i < f->size(); ++i) {
+          double dev = (*f)[i].second - mean;
+          sum_sq_dev += dev * dev;
+        }
+        const double stddev = std::sqrt(sum_sq_dev / f->size());
+        EXPECT_NEAR(kTestMean[midx], mean, 0.1 * kTestMean[midx])
+          << "M=" << kTestMean[midx]
+          << " SD=" << kStdDev
+          << " N=" << kTestSamples[sidx];
+        EXPECT_NEAR(kStdDev, stddev, 0.1 * kStdDev)
+          << "M=" << kTestMean[midx]
+          << " SD=" << kStdDev
+          << " N=" << kTestSamples[sidx];
+        delete f;
+      }
+    }
+  }
+}
diff --git a/talk/base/virtualsocketserver.cc b/talk/base/virtualsocketserver.cc
new file mode 100644
index 0000000..c18ffb6
--- /dev/null
+++ b/talk/base/virtualsocketserver.cc
@@ -0,0 +1,1102 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/virtualsocketserver.h"
+
+#include <errno.h>
+
+#include <algorithm>
+#include <cmath>
+#include <map>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketaddresspair.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+#ifdef WIN32
+const in_addr kInitialNextIPv4 = { {0x01, 0, 0, 0} };
+#else
+// This value is entirely arbitrary, hence the lack of concern about endianness.
+const in_addr kInitialNextIPv4 = { 0x01000000 };
+#endif
+// Starts at ::2 so as to not cause confusion with ::1.
+const in6_addr kInitialNextIPv6 = { { {
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
+    } } };
+
+const uint16 kFirstEphemeralPort = 49152;
+const uint16 kLastEphemeralPort = 65535;
+const uint16 kEphemeralPortCount = kLastEphemeralPort - kFirstEphemeralPort + 1;
+const uint32 kDefaultNetworkCapacity = 64 * 1024;
+const uint32 kDefaultTcpBufferSize = 32 * 1024;
+
+const uint32 UDP_HEADER_SIZE = 28;  // IP + UDP headers
+const uint32 TCP_HEADER_SIZE = 40;  // IP + TCP headers
+const uint32 TCP_MSS = 1400;  // Maximum segment size
+
+// Note: The current algorithm doesn't work for sample sizes smaller than this.
+const int NUM_SAMPLES = 1000;
+
+enum {
+  MSG_ID_PACKET,
+  MSG_ID_CONNECT,
+  MSG_ID_DISCONNECT,
+};
+
+// Packets are passed between sockets as messages.  We copy the data just like
+// the kernel does.
+class Packet : public MessageData {
+ public:
+  Packet(const char* data, size_t size, const SocketAddress& from)
+        : size_(size), consumed_(0), from_(from) {
+    ASSERT(NULL != data);
+    data_ = new char[size_];
+    std::memcpy(data_, data, size_);
+  }
+
+  virtual ~Packet() {
+    delete[] data_;
+  }
+
+  const char* data() const { return data_ + consumed_; }
+  size_t size() const { return size_ - consumed_; }
+  const SocketAddress& from() const { return from_; }
+
+  // Remove the first size bytes from the data.
+  void Consume(size_t size) {
+    ASSERT(size + consumed_ < size_);
+    consumed_ += size;
+  }
+
+ private:
+  char* data_;
+  size_t size_, consumed_;
+  SocketAddress from_;
+};
+
+struct MessageAddress : public MessageData {
+  explicit MessageAddress(const SocketAddress& a) : addr(a) { }
+  SocketAddress addr;
+};
+
+// Implements the socket interface using the virtual network.  Packets are
+// passed as messages using the message queue of the socket server.
+class VirtualSocket : public AsyncSocket, public MessageHandler {
+ public:
+  VirtualSocket(VirtualSocketServer* server, int type, bool async)
+      : server_(server), type_(type), async_(async), state_(CS_CLOSED),
+        listen_queue_(NULL), write_enabled_(false), network_size_(0),
+        recv_buffer_size_(0), bound_(false), was_any_(false) {
+    ASSERT((type_ == SOCK_DGRAM) || (type_ == SOCK_STREAM));
+    ASSERT(async_ || (type_ != SOCK_STREAM));  // We only support async streams
+  }
+
+  virtual ~VirtualSocket() {
+    Close();
+
+    for (RecvBuffer::iterator it = recv_buffer_.begin();
+         it != recv_buffer_.end(); ++it) {
+      delete *it;
+    }
+  }
+
+  virtual SocketAddress GetLocalAddress() const {
+    return local_addr_;
+  }
+
+  virtual SocketAddress GetRemoteAddress() const {
+    return remote_addr_;
+  }
+
+  // Used by server sockets to set the local address without binding.
+  void SetLocalAddress(const SocketAddress& addr) {
+    local_addr_ = addr;
+  }
+
+  virtual int Bind(const SocketAddress& addr) {
+    if (!local_addr_.IsAny()) {
+      error_ = EINVAL;
+      return -1;
+    }
+    local_addr_ = addr;
+    int result = server_->Bind(this, &local_addr_);
+    if (result != 0) {
+      local_addr_.Clear();
+      error_ = EADDRINUSE;
+    } else {
+      bound_ = true;
+      was_any_ = addr.IsAnyIP();
+    }
+    return result;
+  }
+
+  virtual int Connect(const SocketAddress& addr) {
+    return InitiateConnect(addr, true);
+  }
+
+  virtual int Close() {
+    if (!local_addr_.IsAny() && bound_) {
+      // Remove from the binding table.
+      server_->Unbind(local_addr_, this);
+      bound_ = false;
+    }
+
+    if (SOCK_STREAM == type_) {
+      // Cancel pending sockets
+      if (listen_queue_) {
+        while (!listen_queue_->empty()) {
+          SocketAddress addr = listen_queue_->front();
+
+          // Disconnect listening socket.
+          server_->Disconnect(server_->LookupBinding(addr));
+          listen_queue_->pop_front();
+        }
+        delete listen_queue_;
+        listen_queue_ = NULL;
+      }
+      // Disconnect stream sockets
+      if (CS_CONNECTED == state_) {
+        // Disconnect remote socket, check if it is a child of a server socket.
+        VirtualSocket* socket =
+            server_->LookupConnection(local_addr_, remote_addr_);
+        if (!socket) {
+          // Not a server socket child, then see if it is bound.
+          // TODO: If this is indeed a server socket that has no
+          // children this will cause the server socket to be
+          // closed. This might lead to unexpected results, how to fix this?
+          socket = server_->LookupBinding(remote_addr_);
+        }
+        server_->Disconnect(socket);
+
+        // Remove mapping for both directions.
+        server_->RemoveConnection(remote_addr_, local_addr_);
+        server_->RemoveConnection(local_addr_, remote_addr_);
+      }
+      // Cancel potential connects
+      MessageList msgs;
+      if (server_->msg_queue_) {
+        server_->msg_queue_->Clear(this, MSG_ID_CONNECT, &msgs);
+      }
+      for (MessageList::iterator it = msgs.begin(); it != msgs.end(); ++it) {
+        ASSERT(NULL != it->pdata);
+        MessageAddress* data = static_cast<MessageAddress*>(it->pdata);
+
+        // Lookup remote side.
+        VirtualSocket* socket = server_->LookupConnection(local_addr_,
+                                                          data->addr);
+        if (socket) {
+          // Server socket, remote side is a socket retreived by
+          // accept. Accepted sockets are not bound so we will not
+          // find it by looking in the bindings table.
+          server_->Disconnect(socket);
+          server_->RemoveConnection(local_addr_, data->addr);
+        } else {
+          server_->Disconnect(server_->LookupBinding(data->addr));
+        }
+        delete data;
+      }
+      // Clear incoming packets and disconnect messages
+      if (server_->msg_queue_) {
+        server_->msg_queue_->Clear(this);
+      }
+    }
+
+    state_ = CS_CLOSED;
+    local_addr_.Clear();
+    remote_addr_.Clear();
+    return 0;
+  }
+
+  virtual int Send(const void *pv, size_t cb) {
+    if (CS_CONNECTED != state_) {
+      error_ = ENOTCONN;
+      return -1;
+    }
+    if (SOCK_DGRAM == type_) {
+      return SendUdp(pv, cb, remote_addr_);
+    } else {
+      return SendTcp(pv, cb);
+    }
+  }
+
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) {
+    if (SOCK_DGRAM == type_) {
+      return SendUdp(pv, cb, addr);
+    } else {
+      if (CS_CONNECTED != state_) {
+        error_ = ENOTCONN;
+        return -1;
+      }
+      return SendTcp(pv, cb);
+    }
+  }
+
+  virtual int Recv(void *pv, size_t cb) {
+    SocketAddress addr;
+    return RecvFrom(pv, cb, &addr);
+  }
+
+  virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) {
+    // If we don't have a packet, then either error or wait for one to arrive.
+    if (recv_buffer_.empty()) {
+      if (async_) {
+        error_ = EAGAIN;
+        return -1;
+      }
+      while (recv_buffer_.empty()) {
+        Message msg;
+        server_->msg_queue_->Get(&msg);
+        server_->msg_queue_->Dispatch(&msg);
+      }
+    }
+
+    // Return the packet at the front of the queue.
+    Packet* packet = recv_buffer_.front();
+    size_t data_read = _min(cb, packet->size());
+    std::memcpy(pv, packet->data(), data_read);
+    *paddr = packet->from();
+
+    if (data_read < packet->size()) {
+      packet->Consume(data_read);
+    } else {
+      recv_buffer_.pop_front();
+      delete packet;
+    }
+
+    if (SOCK_STREAM == type_) {
+      bool was_full = (recv_buffer_size_ == server_->recv_buffer_capacity_);
+      recv_buffer_size_ -= data_read;
+      if (was_full) {
+        VirtualSocket* sender = server_->LookupBinding(remote_addr_);
+        ASSERT(NULL != sender);
+        server_->SendTcp(sender);
+      }
+    }
+
+    return static_cast<int>(data_read);
+  }
+
+  virtual int Listen(int backlog) {
+    ASSERT(SOCK_STREAM == type_);
+    ASSERT(CS_CLOSED == state_);
+    if (local_addr_.IsAny()) {
+      error_ = EINVAL;
+      return -1;
+    }
+    ASSERT(NULL == listen_queue_);
+    listen_queue_ = new ListenQueue;
+    state_ = CS_CONNECTING;
+    return 0;
+  }
+
+  virtual VirtualSocket* Accept(SocketAddress *paddr) {
+    if (NULL == listen_queue_) {
+      error_ = EINVAL;
+      return NULL;
+    }
+    while (!listen_queue_->empty()) {
+      VirtualSocket* socket = new VirtualSocket(server_, type_, async_);
+
+      // Set the new local address to the same as this server socket.
+      socket->SetLocalAddress(local_addr_);
+      // Sockets made from a socket that 'was Any' need to inherit that.
+      socket->set_was_any(was_any_);
+      SocketAddress remote_addr(listen_queue_->front());
+      int result = socket->InitiateConnect(remote_addr, false);
+      listen_queue_->pop_front();
+      if (result != 0) {
+        delete socket;
+        continue;
+      }
+      socket->CompleteConnect(remote_addr, false);
+      if (paddr) {
+        *paddr = remote_addr;
+      }
+      return socket;
+    }
+    error_ = EWOULDBLOCK;
+    return NULL;
+  }
+
+  virtual int GetError() const {
+    return error_;
+  }
+
+  virtual void SetError(int error) {
+    error_ = error;
+  }
+
+  virtual ConnState GetState() const {
+    return state_;
+  }
+
+  virtual int GetOption(Option opt, int* value) {
+    OptionsMap::const_iterator it = options_map_.find(opt);
+    if (it == options_map_.end()) {
+      return -1;
+    }
+    *value = it->second;
+    return 0;  // 0 is success to emulate getsockopt()
+  }
+
+  virtual int SetOption(Option opt, int value) {
+    options_map_[opt] = value;
+    return 0;  // 0 is success to emulate setsockopt()
+  }
+
+  virtual int EstimateMTU(uint16* mtu) {
+    if (CS_CONNECTED != state_)
+      return ENOTCONN;
+    else
+      return 65536;
+  }
+
+  void OnMessage(Message *pmsg) {
+    if (pmsg->message_id == MSG_ID_PACKET) {
+      //ASSERT(!local_addr_.IsAny());
+      ASSERT(NULL != pmsg->pdata);
+      Packet* packet = static_cast<Packet*>(pmsg->pdata);
+
+      recv_buffer_.push_back(packet);
+
+      if (async_) {
+        SignalReadEvent(this);
+      }
+    } else if (pmsg->message_id == MSG_ID_CONNECT) {
+      ASSERT(NULL != pmsg->pdata);
+      MessageAddress* data = static_cast<MessageAddress*>(pmsg->pdata);
+      if (listen_queue_ != NULL) {
+        listen_queue_->push_back(data->addr);
+        if (async_) {
+          SignalReadEvent(this);
+        }
+      } else if ((SOCK_STREAM == type_) && (CS_CONNECTING == state_)) {
+        CompleteConnect(data->addr, true);
+      } else {
+        LOG(LS_VERBOSE) << "Socket at " << local_addr_ << " is not listening";
+        server_->Disconnect(server_->LookupBinding(data->addr));
+      }
+      delete data;
+    } else if (pmsg->message_id == MSG_ID_DISCONNECT) {
+      ASSERT(SOCK_STREAM == type_);
+      if (CS_CLOSED != state_) {
+        int error = (CS_CONNECTING == state_) ? ECONNREFUSED : 0;
+        state_ = CS_CLOSED;
+        remote_addr_.Clear();
+        if (async_) {
+          SignalCloseEvent(this, error);
+        }
+      }
+    } else {
+      ASSERT(false);
+    }
+  }
+
+  bool was_any() { return was_any_; }
+  void set_was_any(bool was_any) { was_any_ = was_any; }
+
+ private:
+  struct NetworkEntry {
+    uint32 size;
+    uint32 done_time;
+  };
+
+  typedef std::deque<SocketAddress> ListenQueue;
+  typedef std::deque<NetworkEntry> NetworkQueue;
+  typedef std::vector<char> SendBuffer;
+  typedef std::list<Packet*> RecvBuffer;
+  typedef std::map<Option, int> OptionsMap;
+
+  int InitiateConnect(const SocketAddress& addr, bool use_delay) {
+    if (!remote_addr_.IsAny()) {
+      error_ = (CS_CONNECTED == state_) ? EISCONN : EINPROGRESS;
+      return -1;
+    }
+    if (local_addr_.IsAny()) {
+      // If there's no local address set, grab a random one in the correct AF.
+      int result = 0;
+      if (addr.ipaddr().family() == AF_INET) {
+        result = Bind(SocketAddress("0.0.0.0", 0));
+      } else if (addr.ipaddr().family() == AF_INET6) {
+        result = Bind(SocketAddress("::", 0));
+      }
+      if (result != 0) {
+        return result;
+      }
+    }
+    if (type_ == SOCK_DGRAM) {
+      remote_addr_ = addr;
+      state_ = CS_CONNECTED;
+    } else {
+      int result = server_->Connect(this, addr, use_delay);
+      if (result != 0) {
+        error_ = EHOSTUNREACH;
+        return -1;
+      }
+      state_ = CS_CONNECTING;
+    }
+    return 0;
+  }
+
+  void CompleteConnect(const SocketAddress& addr, bool notify) {
+    ASSERT(CS_CONNECTING == state_);
+    remote_addr_ = addr;
+    state_ = CS_CONNECTED;
+    server_->AddConnection(remote_addr_, local_addr_, this);
+    if (async_ && notify) {
+      SignalConnectEvent(this);
+    }
+  }
+
+  int SendUdp(const void* pv, size_t cb, const SocketAddress& addr) {
+    // If we have not been assigned a local port, then get one.
+    if (local_addr_.IsAny()) {
+      int result = server_->Bind(this, &local_addr_);
+      if (result != 0) {
+        local_addr_.Clear();
+        error_ = EADDRINUSE;
+        return result;
+      }
+    }
+
+    // Send the data in a message to the appropriate socket.
+    return server_->SendUdp(this, static_cast<const char*>(pv), cb, addr);
+  }
+
+  int SendTcp(const void* pv, size_t cb) {
+    size_t capacity = server_->send_buffer_capacity_ - send_buffer_.size();
+    if (0 == capacity) {
+      write_enabled_ = true;
+      error_ = EWOULDBLOCK;
+      return -1;
+    }
+    size_t consumed = _min(cb, capacity);
+    const char* cpv = static_cast<const char*>(pv);
+    send_buffer_.insert(send_buffer_.end(), cpv, cpv + consumed);
+    server_->SendTcp(this);
+    return consumed;
+  }
+
+  VirtualSocketServer* server_;
+  int type_;
+  bool async_;
+  ConnState state_;
+  int error_;
+  SocketAddress local_addr_;
+  SocketAddress remote_addr_;
+
+  // Pending sockets which can be Accepted
+  ListenQueue* listen_queue_;
+
+  // Data which tcp has buffered for sending
+  SendBuffer send_buffer_;
+  bool write_enabled_;
+
+  // Critical section to protect the recv_buffer and queue_
+  CriticalSection crit_;
+
+  // Network model that enforces bandwidth and capacity constraints
+  NetworkQueue network_;
+  uint32 network_size_;
+
+  // Data which has been received from the network
+  RecvBuffer recv_buffer_;
+  // The amount of data which is in flight or in recv_buffer_
+  uint32 recv_buffer_size_;
+
+  // Is this socket bound?
+  bool bound_;
+
+  // When we bind a socket to Any, VSS's Bind gives it another address. For
+  // dual-stack sockets, we want to distinguish between sockets that were
+  // explicitly given a particular address and sockets that had one picked
+  // for them by VSS.
+  bool was_any_;
+
+  // Store the options that are set
+  OptionsMap options_map_;
+
+  friend class VirtualSocketServer;
+};
+
+VirtualSocketServer::VirtualSocketServer(SocketServer* ss)
+    : server_(ss), server_owned_(false), msg_queue_(NULL), stop_on_idle_(false),
+      network_delay_(Time()), next_ipv4_(kInitialNextIPv4),
+      next_ipv6_(kInitialNextIPv6), next_port_(kFirstEphemeralPort),
+      bindings_(new AddressMap()), connections_(new ConnectionMap()),
+      bandwidth_(0), network_capacity_(kDefaultNetworkCapacity),
+      send_buffer_capacity_(kDefaultTcpBufferSize),
+      recv_buffer_capacity_(kDefaultTcpBufferSize),
+      delay_mean_(0), delay_stddev_(0), delay_samples_(NUM_SAMPLES),
+      delay_dist_(NULL), drop_prob_(0.0) {
+  if (!server_) {
+    server_ = new PhysicalSocketServer();
+    server_owned_ = true;
+  }
+  UpdateDelayDistribution();
+}
+
+VirtualSocketServer::~VirtualSocketServer() {
+  delete bindings_;
+  delete connections_;
+  delete delay_dist_;
+  if (server_owned_) {
+    delete server_;
+  }
+}
+
+IPAddress VirtualSocketServer::GetNextIP(int family) {
+  if (family == AF_INET) {
+    IPAddress next_ip(next_ipv4_);
+    next_ipv4_.s_addr = htonl(ntohl(next_ipv4_.s_addr) + 1);
+    return next_ip;
+  } else if (family == AF_INET6) {
+    IPAddress next_ip(next_ipv6_);
+    uint32* as_ints = reinterpret_cast<uint32*>(&next_ipv6_.s6_addr);
+    as_ints[3] += 1;
+    return next_ip;
+  }
+  return IPAddress();
+}
+
+uint16 VirtualSocketServer::GetNextPort() {
+  uint16 port = next_port_;
+  if (next_port_ < kLastEphemeralPort) {
+    ++next_port_;
+  } else {
+    next_port_ = kFirstEphemeralPort;
+  }
+  return port;
+}
+
+Socket* VirtualSocketServer::CreateSocket(int type) {
+  return CreateSocketInternal(type);
+}
+
+AsyncSocket* VirtualSocketServer::CreateAsyncSocket(int type) {
+  return CreateSocketInternal(type);
+}
+
+VirtualSocket* VirtualSocketServer::CreateSocketInternal(int type) {
+  return new VirtualSocket(this, type, true);
+}
+
+void VirtualSocketServer::SetMessageQueue(MessageQueue* msg_queue) {
+  msg_queue_ = msg_queue;
+  if (msg_queue_) {
+    msg_queue_->SignalQueueDestroyed.connect(this,
+        &VirtualSocketServer::OnMessageQueueDestroyed);
+  }
+}
+
+bool VirtualSocketServer::Wait(int cmsWait, bool process_io) {
+  ASSERT(msg_queue_ == Thread::Current());
+  if (stop_on_idle_ && Thread::Current()->empty()) {
+    return false;
+  }
+  return socketserver()->Wait(cmsWait, process_io);
+}
+
+void VirtualSocketServer::WakeUp() {
+  socketserver()->WakeUp();
+}
+
+bool VirtualSocketServer::ProcessMessagesUntilIdle() {
+  ASSERT(msg_queue_ == Thread::Current());
+  stop_on_idle_ = true;
+  while (!msg_queue_->empty()) {
+    Message msg;
+    if (msg_queue_->Get(&msg, kForever)) {
+      msg_queue_->Dispatch(&msg);
+    }
+  }
+  stop_on_idle_ = false;
+  return !msg_queue_->IsQuitting();
+}
+
+int VirtualSocketServer::Bind(VirtualSocket* socket,
+                              const SocketAddress& addr) {
+  ASSERT(NULL != socket);
+  // Address must be completely specified at this point
+  ASSERT(!IPIsAny(addr.ipaddr()));
+  ASSERT(addr.port() != 0);
+
+  // Normalize the address (turns v6-mapped addresses into v4-addresses).
+  SocketAddress normalized(addr.ipaddr().Normalized(), addr.port());
+
+  AddressMap::value_type entry(normalized, socket);
+  return bindings_->insert(entry).second ? 0 : -1;
+}
+
+int VirtualSocketServer::Bind(VirtualSocket* socket, SocketAddress* addr) {
+  ASSERT(NULL != socket);
+
+  if (IPIsAny(addr->ipaddr())) {
+    addr->SetIP(GetNextIP(addr->ipaddr().family()));
+  } else {
+    addr->SetIP(addr->ipaddr().Normalized());
+  }
+
+  if (addr->port() == 0) {
+    for (int i = 0; i < kEphemeralPortCount; ++i) {
+      addr->SetPort(GetNextPort());
+      if (bindings_->find(*addr) == bindings_->end()) {
+        break;
+      }
+    }
+  }
+
+  return Bind(socket, *addr);
+}
+
+VirtualSocket* VirtualSocketServer::LookupBinding(const SocketAddress& addr) {
+  SocketAddress normalized(addr.ipaddr().Normalized(),
+                           addr.port());
+  AddressMap::iterator it = bindings_->find(normalized);
+  return (bindings_->end() != it) ? it->second : NULL;
+}
+
+int VirtualSocketServer::Unbind(const SocketAddress& addr,
+                                VirtualSocket* socket) {
+  SocketAddress normalized(addr.ipaddr().Normalized(),
+                           addr.port());
+  ASSERT((*bindings_)[normalized] == socket);
+  bindings_->erase(bindings_->find(normalized));
+  return 0;
+}
+
+void VirtualSocketServer::AddConnection(const SocketAddress& local,
+                                        const SocketAddress& remote,
+                                        VirtualSocket* remote_socket) {
+  // Add this socket pair to our routing table. This will allow
+  // multiple clients to connect to the same server address.
+  SocketAddress local_normalized(local.ipaddr().Normalized(),
+                                 local.port());
+  SocketAddress remote_normalized(remote.ipaddr().Normalized(),
+                                  remote.port());
+  SocketAddressPair address_pair(local_normalized, remote_normalized);
+  connections_->insert(std::pair<SocketAddressPair,
+                       VirtualSocket*>(address_pair, remote_socket));
+}
+
+VirtualSocket* VirtualSocketServer::LookupConnection(
+    const SocketAddress& local,
+    const SocketAddress& remote) {
+  SocketAddress local_normalized(local.ipaddr().Normalized(),
+                                 local.port());
+  SocketAddress remote_normalized(remote.ipaddr().Normalized(),
+                                  remote.port());
+  SocketAddressPair address_pair(local_normalized, remote_normalized);
+  ConnectionMap::iterator it = connections_->find(address_pair);
+  return (connections_->end() != it) ? it->second : NULL;
+}
+
+void VirtualSocketServer::RemoveConnection(const SocketAddress& local,
+                                           const SocketAddress& remote) {
+  SocketAddress local_normalized(local.ipaddr().Normalized(),
+                                local.port());
+  SocketAddress remote_normalized(remote.ipaddr().Normalized(),
+                                 remote.port());
+  SocketAddressPair address_pair(local_normalized, remote_normalized);
+  connections_->erase(address_pair);
+}
+
+static double Random() {
+  return static_cast<double>(rand()) / RAND_MAX;
+}
+
+int VirtualSocketServer::Connect(VirtualSocket* socket,
+                                 const SocketAddress& remote_addr,
+                                 bool use_delay) {
+  uint32 delay = use_delay ? GetRandomTransitDelay() : 0;
+  VirtualSocket* remote = LookupBinding(remote_addr);
+  if (!CanInteractWith(socket, remote)) {
+    LOG(LS_INFO) << "Address family mismatch between "
+                 << socket->GetLocalAddress() << " and " << remote_addr;
+    return -1;
+  }
+  if (remote != NULL) {
+    SocketAddress addr = socket->GetLocalAddress();
+    msg_queue_->PostDelayed(delay, remote, MSG_ID_CONNECT,
+                            new MessageAddress(addr));
+  } else {
+    LOG(LS_INFO) << "No one listening at " << remote_addr;
+    msg_queue_->PostDelayed(delay, socket, MSG_ID_DISCONNECT);
+  }
+  return 0;
+}
+
+bool VirtualSocketServer::Disconnect(VirtualSocket* socket) {
+  if (socket) {
+    // Remove the mapping.
+    msg_queue_->Post(socket, MSG_ID_DISCONNECT);
+    return true;
+  }
+  return false;
+}
+
+int VirtualSocketServer::SendUdp(VirtualSocket* socket,
+                                 const char* data, size_t data_size,
+                                 const SocketAddress& remote_addr) {
+  // See if we want to drop this packet.
+  if (Random() < drop_prob_) {
+    LOG(LS_VERBOSE) << "Dropping packet: bad luck";
+    return static_cast<int>(data_size);
+  }
+
+  VirtualSocket* recipient = LookupBinding(remote_addr);
+  if (!recipient) {
+    // Make a fake recipient for address family checking.
+    scoped_ptr<VirtualSocket> dummy_socket(CreateSocketInternal(SOCK_DGRAM));
+    dummy_socket->SetLocalAddress(remote_addr);
+    if (!CanInteractWith(socket, dummy_socket.get())) {
+      LOG(LS_VERBOSE) << "Incompatible address families: "
+                      << socket->GetLocalAddress() << " and " << remote_addr;
+      return -1;
+    }
+    LOG(LS_VERBOSE) << "No one listening at " << remote_addr;
+    return static_cast<int>(data_size);
+  }
+
+  if (!CanInteractWith(socket, recipient)) {
+    LOG(LS_VERBOSE) << "Incompatible address families: "
+                    << socket->GetLocalAddress() << " and " << remote_addr;
+    return -1;
+  }
+
+  CritScope cs(&socket->crit_);
+
+  uint32 cur_time = Time();
+  PurgeNetworkPackets(socket, cur_time);
+
+  // Determine whether we have enough bandwidth to accept this packet.  To do
+  // this, we need to update the send queue.  Once we know it's current size,
+  // we know whether we can fit this packet.
+  //
+  // NOTE: There are better algorithms for maintaining such a queue (such as
+  // "Derivative Random Drop"); however, this algorithm is a more accurate
+  // simulation of what a normal network would do.
+
+  size_t packet_size = data_size + UDP_HEADER_SIZE;
+  if (socket->network_size_ + packet_size > network_capacity_) {
+    LOG(LS_VERBOSE) << "Dropping packet: network capacity exceeded";
+    return static_cast<int>(data_size);
+  }
+
+  AddPacketToNetwork(socket, recipient, cur_time, data, data_size,
+                     UDP_HEADER_SIZE, false);
+
+  return static_cast<int>(data_size);
+}
+
+void VirtualSocketServer::SendTcp(VirtualSocket* socket) {
+  // TCP can't send more data than will fill up the receiver's buffer.
+  // We track the data that is in the buffer plus data in flight using the
+  // recipient's recv_buffer_size_.  Anything beyond that must be stored in the
+  // sender's buffer.  We will trigger the buffered data to be sent when data
+  // is read from the recv_buffer.
+
+  // Lookup the local/remote pair in the connections table.
+  VirtualSocket* recipient = LookupConnection(socket->local_addr_,
+                                              socket->remote_addr_);
+  if (!recipient) {
+    LOG(LS_VERBOSE) << "Sending data to no one.";
+    return;
+  }
+
+  CritScope cs(&socket->crit_);
+
+  uint32 cur_time = Time();
+  PurgeNetworkPackets(socket, cur_time);
+
+  while (true) {
+    size_t available = recv_buffer_capacity_ - recipient->recv_buffer_size_;
+    size_t max_data_size = _min<size_t>(available, TCP_MSS - TCP_HEADER_SIZE);
+    size_t data_size = _min(socket->send_buffer_.size(), max_data_size);
+    if (0 == data_size)
+      break;
+
+    AddPacketToNetwork(socket, recipient, cur_time, &socket->send_buffer_[0],
+                       data_size, TCP_HEADER_SIZE, true);
+    recipient->recv_buffer_size_ += data_size;
+
+    size_t new_buffer_size = socket->send_buffer_.size() - data_size;
+    // Avoid undefined access beyond the last element of the vector.
+    // This only happens when new_buffer_size is 0.
+    if (data_size < socket->send_buffer_.size()) {
+      // memmove is required for potentially overlapping source/destination.
+      memmove(&socket->send_buffer_[0], &socket->send_buffer_[data_size],
+              new_buffer_size);
+    }
+    socket->send_buffer_.resize(new_buffer_size);
+  }
+
+  if (socket->write_enabled_
+      && (socket->send_buffer_.size() < send_buffer_capacity_)) {
+    socket->write_enabled_ = false;
+    socket->SignalWriteEvent(socket);
+  }
+}
+
+void VirtualSocketServer::AddPacketToNetwork(VirtualSocket* sender,
+                                             VirtualSocket* recipient,
+                                             uint32 cur_time,
+                                             const char* data,
+                                             size_t data_size,
+                                             size_t header_size,
+                                             bool ordered) {
+  VirtualSocket::NetworkEntry entry;
+  entry.size = data_size + header_size;
+
+  sender->network_size_ += entry.size;
+  uint32 send_delay = SendDelay(sender->network_size_);
+  entry.done_time = cur_time + send_delay;
+  sender->network_.push_back(entry);
+
+  // Find the delay for crossing the many virtual hops of the network.
+  uint32 transit_delay = GetRandomTransitDelay();
+
+  // Post the packet as a message to be delivered (on our own thread)
+  Packet* p = new Packet(data, data_size, sender->local_addr_);
+  uint32 ts = TimeAfter(send_delay + transit_delay);
+  if (ordered) {
+    // Ensure that new packets arrive after previous ones
+    // TODO: consider ordering on a per-socket basis, since this
+    // introduces artifical delay.
+    ts = TimeMax(ts, network_delay_);
+  }
+  msg_queue_->PostAt(ts, recipient, MSG_ID_PACKET, p);
+  network_delay_ = TimeMax(ts, network_delay_);
+}
+
+void VirtualSocketServer::PurgeNetworkPackets(VirtualSocket* socket,
+                                              uint32 cur_time) {
+  while (!socket->network_.empty() &&
+         (socket->network_.front().done_time <= cur_time)) {
+    ASSERT(socket->network_size_ >= socket->network_.front().size);
+    socket->network_size_ -= socket->network_.front().size;
+    socket->network_.pop_front();
+  }
+}
+
+uint32 VirtualSocketServer::SendDelay(uint32 size) {
+  if (bandwidth_ == 0)
+    return 0;
+  else
+    return 1000 * size / bandwidth_;
+}
+
+#if 0
+void PrintFunction(std::vector<std::pair<double, double> >* f) {
+  return;
+  double sum = 0;
+  for (uint32 i = 0; i < f->size(); ++i) {
+    std::cout << (*f)[i].first << '\t' << (*f)[i].second << std::endl;
+    sum += (*f)[i].second;
+  }
+  if (!f->empty()) {
+    const double mean = sum / f->size();
+    double sum_sq_dev = 0;
+    for (uint32 i = 0; i < f->size(); ++i) {
+      double dev = (*f)[i].second - mean;
+      sum_sq_dev += dev * dev;
+    }
+    std::cout << "Mean = " << mean << " StdDev = "
+              << sqrt(sum_sq_dev / f->size()) << std::endl;
+  }
+}
+#endif  // <unused>
+
+void VirtualSocketServer::UpdateDelayDistribution() {
+  Function* dist = CreateDistribution(delay_mean_, delay_stddev_,
+                                      delay_samples_);
+  // We take a lock just to make sure we don't leak memory.
+  {
+    CritScope cs(&delay_crit_);
+    delete delay_dist_;
+    delay_dist_ = dist;
+  }
+}
+
+static double PI = 4 * std::atan(1.0);
+
+static double Normal(double x, double mean, double stddev) {
+  double a = (x - mean) * (x - mean) / (2 * stddev * stddev);
+  return std::exp(-a) / (stddev * sqrt(2 * PI));
+}
+
+#if 0  // static unused gives a warning
+static double Pareto(double x, double min, double k) {
+  if (x < min)
+    return 0;
+  else
+    return k * std::pow(min, k) / std::pow(x, k+1);
+}
+#endif
+
+VirtualSocketServer::Function* VirtualSocketServer::CreateDistribution(
+    uint32 mean, uint32 stddev, uint32 samples) {
+  Function* f = new Function();
+
+  if (0 == stddev) {
+    f->push_back(Point(mean, 1.0));
+  } else {
+    double start = 0;
+    if (mean >= 4 * static_cast<double>(stddev))
+      start = mean - 4 * static_cast<double>(stddev);
+    double end = mean + 4 * static_cast<double>(stddev);
+
+    for (uint32 i = 0; i < samples; i++) {
+      double x = start + (end - start) * i / (samples - 1);
+      double y = Normal(x, mean, stddev);
+      f->push_back(Point(x, y));
+    }
+  }
+  return Resample(Invert(Accumulate(f)), 0, 1, samples);
+}
+
+uint32 VirtualSocketServer::GetRandomTransitDelay() {
+  size_t index = rand() % delay_dist_->size();
+  double delay = (*delay_dist_)[index].second;
+  //LOG_F(LS_INFO) << "random[" << index << "] = " << delay;
+  return static_cast<uint32>(delay);
+}
+
+struct FunctionDomainCmp {
+  bool operator()(const VirtualSocketServer::Point& p1,
+                   const VirtualSocketServer::Point& p2) {
+    return p1.first < p2.first;
+  }
+  bool operator()(double v1, const VirtualSocketServer::Point& p2) {
+    return v1 < p2.first;
+  }
+  bool operator()(const VirtualSocketServer::Point& p1, double v2) {
+    return p1.first < v2;
+  }
+};
+
+VirtualSocketServer::Function* VirtualSocketServer::Accumulate(Function* f) {
+  ASSERT(f->size() >= 1);
+  double v = 0;
+  for (Function::size_type i = 0; i < f->size() - 1; ++i) {
+    double dx = (*f)[i + 1].first - (*f)[i].first;
+    double avgy = ((*f)[i + 1].second + (*f)[i].second) / 2;
+    (*f)[i].second = v;
+    v = v + dx * avgy;
+  }
+  (*f)[f->size()-1].second = v;
+  return f;
+}
+
+VirtualSocketServer::Function* VirtualSocketServer::Invert(Function* f) {
+  for (Function::size_type i = 0; i < f->size(); ++i)
+    std::swap((*f)[i].first, (*f)[i].second);
+
+  std::sort(f->begin(), f->end(), FunctionDomainCmp());
+  return f;
+}
+
+VirtualSocketServer::Function* VirtualSocketServer::Resample(
+    Function* f, double x1, double x2, uint32 samples) {
+  Function* g = new Function();
+
+  for (size_t i = 0; i < samples; i++) {
+    double x = x1 + (x2 - x1) * i / (samples - 1);
+    double y = Evaluate(f, x);
+    g->push_back(Point(x, y));
+  }
+
+  delete f;
+  return g;
+}
+
+double VirtualSocketServer::Evaluate(Function* f, double x) {
+  Function::iterator iter =
+      std::lower_bound(f->begin(), f->end(), x, FunctionDomainCmp());
+  if (iter == f->begin()) {
+    return (*f)[0].second;
+  } else if (iter == f->end()) {
+    ASSERT(f->size() >= 1);
+    return (*f)[f->size() - 1].second;
+  } else if (iter->first == x) {
+    return iter->second;
+  } else {
+    double x1 = (iter - 1)->first;
+    double y1 = (iter - 1)->second;
+    double x2 = iter->first;
+    double y2 = iter->second;
+    return y1 + (y2 - y1) * (x - x1) / (x2 - x1);
+  }
+}
+
+bool VirtualSocketServer::CanInteractWith(VirtualSocket* local,
+                                          VirtualSocket* remote) {
+  if (!local || !remote) {
+    return false;
+  }
+  IPAddress local_ip = local->GetLocalAddress().ipaddr();
+  IPAddress remote_ip = remote->GetLocalAddress().ipaddr();
+  IPAddress local_normalized = local_ip.Normalized();
+  IPAddress remote_normalized = remote_ip.Normalized();
+  // Check if the addresses are the same family after Normalization (turns
+  // mapped IPv6 address into IPv4 addresses).
+  // This will stop unmapped V6 addresses from talking to mapped V6 addresses.
+  if (local_normalized.family() == remote_normalized.family()) {
+    return true;
+  }
+
+  // If ip1 is IPv4 and ip2 is :: and ip2 is not IPV6_V6ONLY.
+  int remote_v6_only = 0;
+  remote->GetOption(Socket::OPT_IPV6_V6ONLY, &remote_v6_only);
+  if (local_ip.family() == AF_INET && !remote_v6_only && IPIsAny(remote_ip)) {
+    return true;
+  }
+  // Same check, backwards.
+  int local_v6_only = 0;
+  local->GetOption(Socket::OPT_IPV6_V6ONLY, &local_v6_only);
+  if (remote_ip.family() == AF_INET && !local_v6_only && IPIsAny(local_ip)) {
+    return true;
+  }
+
+  // Check to see if either socket was explicitly bound to IPv6-any.
+  // These sockets can talk with anyone.
+  if (local_ip.family() == AF_INET6 && local->was_any()) {
+    return true;
+  }
+  if (remote_ip.family() == AF_INET6 && remote->was_any()) {
+    return true;
+  }
+
+  return false;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/virtualsocketserver.h b/talk/base/virtualsocketserver.h
new file mode 100644
index 0000000..d35d586
--- /dev/null
+++ b/talk/base/virtualsocketserver.h
@@ -0,0 +1,247 @@
+/*
+ * 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_VIRTUALSOCKETSERVER_H_
+#define TALK_BASE_VIRTUALSOCKETSERVER_H_
+
+#include <cassert>
+#include <deque>
+#include <map>
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/socketserver.h"
+
+namespace talk_base {
+
+class VirtualSocket;
+class SocketAddressPair;
+
+// Simulates a network in the same manner as a loopback interface.  The
+// interface can create as many addresses as you want.  All of the sockets
+// created by this network will be able to communicate with one another, unless
+// they are bound to addresses from incompatible families.
+class VirtualSocketServer : public SocketServer, public sigslot::has_slots<> {
+ public:
+  // TODO: Add "owned" parameter.
+  // If "owned" is set, the supplied socketserver will be deleted later.
+  explicit VirtualSocketServer(SocketServer* ss);
+  virtual ~VirtualSocketServer();
+
+  SocketServer* socketserver() { return server_; }
+
+  // Limits the network bandwidth (maximum bytes per second).  Zero means that
+  // all sends occur instantly.  Defaults to 0.
+  uint32 bandwidth() const { return bandwidth_; }
+  void set_bandwidth(uint32 bandwidth) { bandwidth_ = bandwidth; }
+
+  // Limits the amount of data which can be in flight on the network without
+  // packet loss (on a per sender basis).  Defaults to 64 KB.
+  uint32 network_capacity() const { return network_capacity_; }
+  void set_network_capacity(uint32 capacity) {
+    network_capacity_ = capacity;
+  }
+
+  // The amount of data which can be buffered by tcp on the sender's side
+  uint32 send_buffer_capacity() const { return send_buffer_capacity_; }
+  void set_send_buffer_capacity(uint32 capacity) {
+    send_buffer_capacity_ = capacity;
+  }
+
+  // The amount of data which can be buffered by tcp on the receiver's side
+  uint32 recv_buffer_capacity() const { return recv_buffer_capacity_; }
+  void set_recv_buffer_capacity(uint32 capacity) {
+    recv_buffer_capacity_ = capacity;
+  }
+
+  // Controls the (transit) delay for packets sent in the network.  This does
+  // not inclue the time required to sit in the send queue.  Both of these
+  // values are measured in milliseconds.  Defaults to no delay.
+  uint32 delay_mean() const { return delay_mean_; }
+  uint32 delay_stddev() const { return delay_stddev_; }
+  uint32 delay_samples() const { return delay_samples_; }
+  void set_delay_mean(uint32 delay_mean) { delay_mean_ = delay_mean; }
+  void set_delay_stddev(uint32 delay_stddev) {
+    delay_stddev_ = delay_stddev;
+  }
+  void set_delay_samples(uint32 delay_samples) {
+    delay_samples_ = delay_samples;
+  }
+
+  // If the (transit) delay parameters are modified, this method should be
+  // called to recompute the new distribution.
+  void UpdateDelayDistribution();
+
+  // Controls the (uniform) probability that any sent packet is dropped.  This
+  // is separate from calculations to drop based on queue size.
+  double drop_probability() { return drop_prob_; }
+  void set_drop_probability(double drop_prob) {
+    assert((0 <= drop_prob) && (drop_prob <= 1));
+    drop_prob_ = drop_prob;
+  }
+
+  // SocketFactory:
+  virtual Socket* CreateSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+
+  // SocketServer:
+  virtual void SetMessageQueue(MessageQueue* queue);
+  virtual bool Wait(int cms, bool process_io);
+  virtual void WakeUp();
+
+  typedef std::pair<double, double> Point;
+  typedef std::vector<Point> Function;
+
+  static Function* CreateDistribution(uint32 mean, uint32 stddev,
+                                      uint32 samples);
+
+  // Similar to Thread::ProcessMessages, but it only processes messages until
+  // there are no immediate messages or pending network traffic.  Returns false
+  // if Thread::Stop() was called.
+  bool ProcessMessagesUntilIdle();
+
+ protected:
+  // Returns a new IP not used before in this network.
+  IPAddress GetNextIP(int family);
+  uint16 GetNextPort();
+
+  VirtualSocket* CreateSocketInternal(int type);
+
+  // Binds the given socket to addr, assigning and IP and Port if necessary
+  int Bind(VirtualSocket* socket, SocketAddress* addr);
+
+  // Binds the given socket to the given (fully-defined) address.
+  int Bind(VirtualSocket* socket, const SocketAddress& addr);
+
+  // Find the socket bound to the given address
+  VirtualSocket* LookupBinding(const SocketAddress& addr);
+
+  int Unbind(const SocketAddress& addr, VirtualSocket* socket);
+
+  // Adds a mapping between this socket pair and the socket.
+  void AddConnection(const SocketAddress& client,
+                     const SocketAddress& server,
+                     VirtualSocket* socket);
+
+  // Find the socket pair corresponding to this server address.
+  VirtualSocket* LookupConnection(const SocketAddress& client,
+                                  const SocketAddress& server);
+
+  void RemoveConnection(const SocketAddress& client,
+                        const SocketAddress& server);
+
+  // Connects the given socket to the socket at the given address
+  int Connect(VirtualSocket* socket, const SocketAddress& remote_addr,
+              bool use_delay);
+
+  // Sends a disconnect message to the socket at the given address
+  bool Disconnect(VirtualSocket* socket);
+
+  // Sends the given packet to the socket at the given address (if one exists).
+  int SendUdp(VirtualSocket* socket, const char* data, size_t data_size,
+              const SocketAddress& remote_addr);
+
+  // Moves as much data as possible from the sender's buffer to the network
+  void SendTcp(VirtualSocket* socket);
+
+  // Places a packet on the network.
+  void AddPacketToNetwork(VirtualSocket* socket, VirtualSocket* recipient,
+                          uint32 cur_time, const char* data, size_t data_size,
+                          size_t header_size, bool ordered);
+
+  // Removes stale packets from the network
+  void PurgeNetworkPackets(VirtualSocket* socket, uint32 cur_time);
+
+  // Computes the number of milliseconds required to send a packet of this size.
+  uint32 SendDelay(uint32 size);
+
+  // Returns a random transit delay chosen from the appropriate distribution.
+  uint32 GetRandomTransitDelay();
+
+  // Basic operations on functions.  Those that return a function also take
+  // ownership of the function given (and hence, may modify or delete it).
+  static Function* Accumulate(Function* f);
+  static Function* Invert(Function* f);
+  static Function* Resample(Function* f, double x1, double x2, uint32 samples);
+  static double Evaluate(Function* f, double x);
+
+  // NULL out our message queue if it goes away. Necessary in the case where
+  // our lifetime is greater than that of the thread we are using, since we
+  // try to send Close messages for all connected sockets when we shutdown.
+  void OnMessageQueueDestroyed() { msg_queue_ = NULL; }
+
+  // Determine if two sockets should be able to communicate.
+  // We don't (currently) specify an address family for sockets; instead,
+  // the currently bound address is used to infer the address family.
+  // Any socket that is not explicitly bound to an IPv4 address is assumed to be
+  // dual-stack capable.
+  // This function tests if two addresses can communicate, as well as the
+  // sockets to which they may be bound (the addresses may or may not yet be
+  // bound to the sockets).
+  // First the addresses are tested (after normalization):
+  // If both have the same family, then communication is OK.
+  // If only one is IPv4 then false, unless the other is bound to ::.
+  // This applies even if the IPv4 address is 0.0.0.0.
+  // The socket arguments are optional; the sockets are checked to see if they
+  // were explicitly bound to IPv6-any ('::'), and if so communication is
+  // permitted.
+  // NB: This scheme doesn't permit non-dualstack IPv6 sockets.
+  static bool CanInteractWith(VirtualSocket* local, VirtualSocket* remote);
+
+ private:
+  friend class VirtualSocket;
+
+  typedef std::map<SocketAddress, VirtualSocket*> AddressMap;
+  typedef std::map<SocketAddressPair, VirtualSocket*> ConnectionMap;
+
+  SocketServer* server_;
+  bool server_owned_;
+  MessageQueue* msg_queue_;
+  bool stop_on_idle_;
+  uint32 network_delay_;
+  in_addr next_ipv4_;
+  in6_addr next_ipv6_;
+  uint16 next_port_;
+  AddressMap* bindings_;
+  ConnectionMap* connections_;
+
+  uint32 bandwidth_;
+  uint32 network_capacity_;
+  uint32 send_buffer_capacity_;
+  uint32 recv_buffer_capacity_;
+  uint32 delay_mean_;
+  uint32 delay_stddev_;
+  uint32 delay_samples_;
+  Function* delay_dist_;
+  CriticalSection delay_crit_;
+
+  double drop_prob_;
+  DISALLOW_EVIL_CONSTRUCTORS(VirtualSocketServer);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_VIRTUALSOCKETSERVER_H_
diff --git a/talk/base/win32.cc b/talk/base/win32.cc
index 5e28aa9..3937781 100644
--- a/talk/base/win32.cc
+++ b/talk/base/win32.cc
@@ -26,13 +26,272 @@
  */
 
 #include "talk/base/win32.h"
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
 #include <algorithm>
 
 #include "talk/base/basictypes.h"
 #include "talk/base/common.h"
+#include "talk/base/logging.h"
 
 namespace talk_base {
 
+// Helper function declarations for inet_ntop/inet_pton.
+static const char* inet_ntop_v4(const void* src, char* dst, socklen_t size);
+static const char* inet_ntop_v6(const void* src, char* dst, socklen_t size);
+static int inet_pton_v4(const char* src, void* dst);
+static int inet_pton_v6(const char* src, void* dst);
+
+// Implementation of inet_ntop (create a printable representation of an
+// ip address). XP doesn't have its own inet_ntop, and
+// WSAAddressToString requires both IPv6 to be  installed and for Winsock
+// to be initialized.
+const char* win32_inet_ntop(int af, const void *src,
+                            char* dst, socklen_t size) {
+  if (!src || !dst) {
+    return NULL;
+  }
+  switch (af) {
+    case AF_INET: {
+      return inet_ntop_v4(src, dst, size);
+    }
+    case AF_INET6: {
+      return inet_ntop_v6(src, dst, size);
+    }
+  }
+  return NULL;
+}
+
+// As above, but for inet_pton. Wraps inet_addr for v4, and implements inet_pton
+// for v6. Slightly more permissive than the RFC specified inet_pton, as it uses
+// windows' inet_addr which permits octal and hexadecimal values in v4
+// addresses, while inet_pton only allows decimal.
+// Note that our inet_ntop will output normal 'dotted' v4 addresses only.
+int win32_inet_pton(int af, const char* src, void* dst) {
+  if (!src || !dst) {
+    return 0;
+  }
+  if (af == AF_INET) {
+    return inet_pton_v4(src, dst);
+  } else if (af == AF_INET6) {
+    return inet_pton_v6(src, dst);
+  }
+  return -1;
+}
+
+// Helper function for inet_ntop for IPv4 addresses.
+// Outputs "dotted-quad" decimal notation.
+const char* inet_ntop_v4(const void* src, char* dst, socklen_t size) {
+  if (size < INET_ADDRSTRLEN) {
+    return NULL;
+  }
+  const struct in_addr* as_in_addr =
+      reinterpret_cast<const struct in_addr*>(src);
+  talk_base::sprintfn(dst, size, "%d.%d.%d.%d",
+                      as_in_addr->S_un.S_un_b.s_b1,
+                      as_in_addr->S_un.S_un_b.s_b2,
+                      as_in_addr->S_un.S_un_b.s_b3,
+                      as_in_addr->S_un.S_un_b.s_b4);
+  return dst;
+}
+
+// Helper function for inet_ntop for IPv6 addresses.
+const char* inet_ntop_v6(const void* src, char* dst, socklen_t size) {
+  if (size < INET6_ADDRSTRLEN) {
+    return NULL;
+  }
+  const uint16* as_shorts =
+      reinterpret_cast<const uint16*>(src);
+  int runpos[8];
+  int current = 1;
+  int max = 1;
+  int maxpos = -1;
+  int run_array_size = ARRAY_SIZE(runpos);
+  // Run over the address marking runs of 0s.
+  for (int i = 0; i < run_array_size; ++i) {
+    if (as_shorts[i] == 0) {
+      runpos[i] = current;
+      if (current > max) {
+        maxpos = i;
+        max = current;
+      }
+      ++current;
+    } else {
+      runpos[i] = -1;
+      current =1;
+    }
+  }
+
+  if (max > 1) {
+    int tmpmax = maxpos;
+    // Run back through, setting -1 for all but the longest run.
+    for (int i = run_array_size - 1; i >= 0; i--) {
+      if (i > tmpmax) {
+        runpos[i] = -1;
+      } else if (runpos[i] == -1) {
+        // We're less than maxpos, we hit a -1, so the 'good' run is done.
+        // Setting tmpmax -1 means all remaining positions get set to -1.
+        tmpmax = -1;
+      }
+    }
+  }
+
+  char* cursor = dst;
+  // Print IPv4 compatible and IPv4 mapped addresses using the IPv4 helper.
+  // These addresses have an initial run of either eight zero-bytes followed
+  // by 0xFFFF, or an initial run of ten zero-bytes.
+  if (runpos[0] == 1 && (maxpos == 5 ||
+                         (maxpos == 4 && as_shorts[5] == 0xFFFF))) {
+    *cursor++ = ':';
+    *cursor++ = ':';
+    if (maxpos == 4) {
+      cursor += talk_base::sprintfn(cursor, INET6_ADDRSTRLEN - 2, "ffff:");
+    }
+    const struct in_addr* as_v4 =
+        reinterpret_cast<const struct in_addr*>(&(as_shorts[6]));
+    inet_ntop_v4(as_v4, cursor, (INET6_ADDRSTRLEN - (cursor - dst)));
+  } else {
+    for (int i = 0; i < run_array_size; ++i) {
+      if (runpos[i] == -1) {
+        cursor += talk_base::sprintfn(cursor,
+                                      INET6_ADDRSTRLEN - (cursor - dst),
+                                      "%x", ntohs(as_shorts[i]));
+        if (i != 7 && runpos[i + 1] != 1) {
+          *cursor++ = ':';
+        }
+      } else if (runpos[i] == 1) {
+        // Entered the run; print the colons and skip the run.
+        *cursor++ = ':';
+        *cursor++ = ':';
+        i += (max - 1);
+      }
+    }
+  }
+  return dst;
+}
+
+// Helper function for inet_pton for IPv4 addresses.
+// Uses win32's inet_addr.
+int inet_pton_v4(const char* src, void* dst) {
+  uint32 ip = inet_addr(src);
+  if (ip == 0xFFFFFFFF && strcmp(src, "255.255.255.255") != 0) {
+    return 0;
+  }
+  struct in_addr* dst_as_in_addr = reinterpret_cast<struct in_addr*>(dst);
+  dst_as_in_addr->s_addr = ip;
+  return 1;
+}
+
+// Helper function for inet_pton for IPv6 addresses.
+int inet_pton_v6(const char* src, void* dst) {
+  // sscanf will pick any other invalid chars up, but it parses 0xnnnn as hex.
+  // Check for literal x in the input string.
+  const char* readcursor = src;
+  char c = *readcursor++;
+  while (c) {
+    if (c == 'x') {
+      return 0;
+    }
+    c = *readcursor++;
+  }
+  readcursor = src;
+
+  struct in6_addr an_addr;
+  memset(&an_addr, 0, sizeof(an_addr));
+
+  uint16* addr_cursor = reinterpret_cast<uint16*>(&an_addr.s6_addr[0]);
+  uint16* addr_end = reinterpret_cast<uint16*>(&an_addr.s6_addr[16]);
+  bool seencompressed = false;
+
+  // Addresses that start with "::" (i.e., a run of initial zeros) or
+  // "::ffff:" can potentially be IPv4 mapped or compatibility addresses.
+  // These have dotted-style IPv4 addresses on the end (e.g. "::192.168.7.1").
+  if (*readcursor == ':' && *(readcursor+1) == ':' &&
+      *(readcursor + 2) != 0) {
+    // Check for periods, which we'll take as a sign of v4 addresses.
+    const char* addrstart = readcursor + 2;
+    if (talk_base::strchr(addrstart, ".")) {
+      const char* colon = talk_base::strchr(addrstart, "::");
+      if (colon) {
+        uint16 a_short;
+        int bytesread = 0;
+        if (sscanf(addrstart, "%hx%n", &a_short, &bytesread) != 1 ||
+            a_short != 0xFFFF || bytesread != 4) {
+          // Colons + periods means has to be ::ffff:a.b.c.d. But it wasn't.
+          return 0;
+        } else {
+          an_addr.s6_addr[10] = 0xFF;
+          an_addr.s6_addr[11] = 0xFF;
+          addrstart = colon + 1;
+        }
+      }
+      struct in_addr v4;
+      if (inet_pton_v4(addrstart, &v4.s_addr)) {
+        memcpy(&an_addr.s6_addr[12], &v4, sizeof(v4));
+        memcpy(dst, &an_addr, sizeof(an_addr));
+        return 1;
+      } else {
+        // Invalid v4 address.
+        return 0;
+      }
+    }
+  }
+
+  // For addresses without a trailing IPv4 component ('normal' IPv6 addresses).
+  while (*readcursor != 0 && addr_cursor < addr_end) {
+    if (*readcursor == ':') {
+      if (*(readcursor + 1) == ':') {
+        if (seencompressed) {
+          // Can only have one compressed run of zeroes ("::") per address.
+          return 0;
+        }
+        // Hit a compressed run. Count colons to figure out how much of the
+        // address is skipped.
+        readcursor += 2;
+        const char* coloncounter = readcursor;
+        int coloncount = 0;
+        if (*coloncounter == 0) {
+          // Special case - trailing ::.
+          addr_cursor = addr_end;
+        } else {
+          while (*coloncounter) {
+            if (*coloncounter == ':') {
+              ++coloncount;
+            }
+            ++coloncounter;
+          }
+          // (coloncount + 1) is the number of shorts left in the address.
+          addr_cursor = addr_end - (coloncount + 1);
+          seencompressed = true;
+        }
+      } else {
+        ++readcursor;
+      }
+    } else {
+      uint16 word;
+      int bytesread = 0;
+      if (sscanf(readcursor, "%hx%n", &word, &bytesread) != 1) {
+        return 0;
+      } else {
+        *addr_cursor = htons(word);
+        ++addr_cursor;
+        readcursor += bytesread;
+        if (*readcursor != ':' && *readcursor != '\0') {
+          return 0;
+        }
+      }
+    }
+  }
+
+  if (*readcursor != '\0' || addr_cursor < addr_end) {
+    // Catches addresses too short or too long.
+    return 0;
+  }
+  memcpy(dst, &an_addr, sizeof(an_addr));
+  return 1;
+}
+
 //
 // Unix time is in seconds relative to 1/1/1970.  So we compute the windows
 // FILETIME of that time/date, then we add/subtract in appropriate units to
@@ -178,6 +437,4 @@
   }
   return ret;
 }
-
 }  // namespace talk_base
-
diff --git a/talk/base/win32.h b/talk/base/win32.h
index 6274b0e..9c6ea3f 100644
--- a/talk/base/win32.h
+++ b/talk/base/win32.h
@@ -54,6 +54,9 @@
 
 namespace talk_base {
 
+const char* win32_inet_ntop(int af, const void *src, char* dst, socklen_t size);
+int win32_inet_pton(int af, const char* src, void *dst);
+
 ///////////////////////////////////////////////////////////////////////////////
 
 inline std::wstring ToUtf16(const char* utf8, size_t len) {
diff --git a/talk/base/win32_unittest.cc b/talk/base/win32_unittest.cc
new file mode 100644
index 0000000..4800e6e
--- /dev/null
+++ b/talk/base/win32_unittest.cc
@@ -0,0 +1,54 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/win32.h"
+
+#ifndef WIN32
+#error Only for Windows
+#endif
+
+namespace talk_base {
+
+class Win32Test : public testing::Test {
+ public:
+  Win32Test() {
+  }
+};
+
+TEST_F(Win32Test, FileTimeToUInt64Test) {
+  FILETIME ft;
+  ft.dwHighDateTime = 0xBAADF00D;
+  ft.dwLowDateTime = 0xFEED3456;
+
+  uint64 expected = 0xBAADF00DFEED3456;
+  EXPECT_EQ(expected, ToUInt64(ft));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32regkey.cc b/talk/base/win32regkey.cc
new file mode 100644
index 0000000..de74327
--- /dev/null
+++ b/talk/base/win32regkey.cc
@@ -0,0 +1,1119 @@
+/*
+ * libjingle
+ * Copyright 2003-2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Registry configuration wrapers class implementation
+//
+// Change made by S. Ganesh - ganesh@google.com:
+//   Use SHQueryValueEx instead of RegQueryValueEx throughout.
+//   A call to the SHLWAPI function is essentially a call to the standard
+//   function but with post-processing:
+//   * to fix REG_SZ or REG_EXPAND_SZ data that is not properly null-terminated;
+//   * to expand REG_EXPAND_SZ data.
+
+#include "talk/base/win32regkey.h"
+
+#include <shlwapi.h>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+RegKey::RegKey() {
+  h_key_ = NULL;
+}
+
+RegKey::~RegKey() {
+  Close();
+}
+
+HRESULT RegKey::Create(HKEY parent_key, const wchar_t* key_name) {
+  return Create(parent_key,
+                key_name,
+                REG_NONE,
+                REG_OPTION_NON_VOLATILE,
+                KEY_ALL_ACCESS,
+                NULL,
+                NULL);
+}
+
+HRESULT RegKey::Open(HKEY parent_key, const wchar_t* key_name) {
+  return Open(parent_key, key_name, KEY_ALL_ACCESS);
+}
+
+bool RegKey::HasValue(const TCHAR* value_name) const {
+  return (ERROR_SUCCESS == ::RegQueryValueEx(h_key_, value_name, NULL,
+                                             NULL, NULL, NULL));
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         DWORD value) {
+  ASSERT(full_key_name != NULL);
+
+  return SetValueStaticHelper(full_key_name, value_name, REG_DWORD, &value);
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         DWORD64 value) {
+  ASSERT(full_key_name != NULL);
+
+  return SetValueStaticHelper(full_key_name, value_name, REG_QWORD, &value);
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         float value) {
+  ASSERT(full_key_name != NULL);
+
+  return SetValueStaticHelper(full_key_name, value_name,
+                              REG_BINARY, &value, sizeof(value));
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         double value) {
+  ASSERT(full_key_name != NULL);
+
+  return SetValueStaticHelper(full_key_name, value_name,
+                              REG_BINARY, &value, sizeof(value));
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         const TCHAR* value) {
+  ASSERT(full_key_name != NULL);
+  ASSERT(value != NULL);
+
+  return SetValueStaticHelper(full_key_name, value_name,
+                              REG_SZ, const_cast<wchar_t*>(value));
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         const uint8* value,
+                         DWORD byte_count) {
+  ASSERT(full_key_name != NULL);
+
+  return SetValueStaticHelper(full_key_name, value_name, REG_BINARY,
+                              const_cast<uint8*>(value), byte_count);
+}
+
+HRESULT RegKey::SetValueMultiSZ(const wchar_t* full_key_name,
+                                const wchar_t* value_name,
+                                const uint8* value,
+                                DWORD byte_count) {
+  ASSERT(full_key_name != NULL);
+
+  return SetValueStaticHelper(full_key_name, value_name, REG_MULTI_SZ,
+                              const_cast<uint8*>(value), byte_count);
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         DWORD* value) {
+  ASSERT(full_key_name != NULL);
+  ASSERT(value != NULL);
+
+  return GetValueStaticHelper(full_key_name, value_name, REG_DWORD, value);
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         DWORD64* value) {
+  ASSERT(full_key_name != NULL);
+  ASSERT(value != NULL);
+
+  return GetValueStaticHelper(full_key_name, value_name, REG_QWORD, value);
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         float* value) {
+  ASSERT(value != NULL);
+  ASSERT(full_key_name != NULL);
+
+  DWORD byte_count = 0;
+  scoped_array<byte> buffer;
+  HRESULT hr = GetValueStaticHelper(full_key_name, value_name,
+                                    REG_BINARY, buffer.accept(), &byte_count);
+  if (SUCCEEDED(hr)) {
+    ASSERT(byte_count == sizeof(*value));
+    if (byte_count == sizeof(*value)) {
+      *value = *reinterpret_cast<float*>(buffer.get());
+    }
+  }
+  return hr;
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         double* value) {
+  ASSERT(value != NULL);
+  ASSERT(full_key_name != NULL);
+
+  DWORD byte_count = 0;
+  scoped_array<byte> buffer;
+  HRESULT hr = GetValueStaticHelper(full_key_name, value_name,
+                                    REG_BINARY, buffer.accept(), &byte_count);
+  if (SUCCEEDED(hr)) {
+    ASSERT(byte_count == sizeof(*value));
+    if (byte_count == sizeof(*value)) {
+      *value = *reinterpret_cast<double*>(buffer.get());
+    }
+  }
+  return hr;
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         wchar_t** value) {
+  ASSERT(full_key_name != NULL);
+  ASSERT(value != NULL);
+
+  return GetValueStaticHelper(full_key_name, value_name, REG_SZ, value);
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         std::wstring* value) {
+  ASSERT(full_key_name != NULL);
+  ASSERT(value != NULL);
+
+  scoped_array<wchar_t> buffer;
+  HRESULT hr = RegKey::GetValue(full_key_name, value_name, buffer.accept());
+  if (SUCCEEDED(hr)) {
+    value->assign(buffer.get());
+  }
+  return hr;
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         std::vector<std::wstring>* value) {
+  ASSERT(full_key_name != NULL);
+  ASSERT(value != NULL);
+
+  return GetValueStaticHelper(full_key_name, value_name, REG_MULTI_SZ, value);
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+                         const wchar_t* value_name,
+                         uint8** value,
+                         DWORD* byte_count) {
+  ASSERT(full_key_name != NULL);
+  ASSERT(value != NULL);
+  ASSERT(byte_count != NULL);
+
+  return GetValueStaticHelper(full_key_name, value_name,
+                              REG_BINARY, value, byte_count);
+}
+
+HRESULT RegKey::DeleteSubKey(const wchar_t* key_name) {
+  ASSERT(key_name != NULL);
+  ASSERT(h_key_ != NULL);
+
+  LONG res = ::RegDeleteKey(h_key_, key_name);
+  HRESULT hr = HRESULT_FROM_WIN32(res);
+  if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
+      hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) {
+    hr = S_FALSE;
+  }
+  return hr;
+}
+
+HRESULT RegKey::DeleteValue(const wchar_t* value_name) {
+  ASSERT(h_key_ != NULL);
+
+  LONG res = ::RegDeleteValue(h_key_, value_name);
+  HRESULT hr = HRESULT_FROM_WIN32(res);
+  if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
+      hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) {
+    hr = S_FALSE;
+  }
+  return hr;
+}
+
+HRESULT RegKey::Close() {
+  HRESULT hr = S_OK;
+  if (h_key_ != NULL) {
+    LONG res = ::RegCloseKey(h_key_);
+    hr = HRESULT_FROM_WIN32(res);
+    h_key_ = NULL;
+  }
+  return hr;
+}
+
+HRESULT RegKey::Create(HKEY parent_key,
+                       const wchar_t* key_name,
+                       wchar_t* lpszClass,
+                       DWORD options,
+                       REGSAM sam_desired,
+                       LPSECURITY_ATTRIBUTES lpSecAttr,
+                       LPDWORD lpdwDisposition) {
+  ASSERT(key_name != NULL);
+  ASSERT(parent_key != NULL);
+
+  DWORD dw = 0;
+  HKEY h_key = NULL;
+  LONG res = ::RegCreateKeyEx(parent_key, key_name, 0, lpszClass, options,
+                              sam_desired, lpSecAttr, &h_key, &dw);
+  HRESULT hr = HRESULT_FROM_WIN32(res);
+
+  if (lpdwDisposition) {
+    *lpdwDisposition = dw;
+  }
+
+  // we have to close the currently opened key
+  // before replacing it with the new one
+  if (hr == S_OK) {
+    hr = Close();
+    ASSERT(hr == S_OK);
+    h_key_ = h_key;
+  }
+  return hr;
+}
+
+HRESULT RegKey::Open(HKEY parent_key,
+                     const wchar_t* key_name,
+                     REGSAM sam_desired) {
+  ASSERT(key_name != NULL);
+  ASSERT(parent_key != NULL);
+
+  HKEY h_key = NULL;
+  LONG res = ::RegOpenKeyEx(parent_key, key_name, 0, sam_desired, &h_key);
+  HRESULT hr = HRESULT_FROM_WIN32(res);
+
+  // we have to close the currently opened key
+  // before replacing it with the new one
+  if (hr == S_OK) {
+    // close the currently opened key if any
+    hr = Close();
+    ASSERT(hr == S_OK);
+    h_key_ = h_key;
+  }
+  return hr;
+}
+
+// save the key and all of its subkeys and values to a file
+HRESULT RegKey::Save(const wchar_t* full_key_name, const wchar_t* file_name) {
+  ASSERT(full_key_name != NULL);
+  ASSERT(file_name != NULL);
+
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+  if (!h_key) {
+    return E_FAIL;
+  }
+
+  RegKey key;
+  HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  AdjustCurrentProcessPrivilege(SE_BACKUP_NAME, true);
+  LONG res = ::RegSaveKey(key.h_key_, file_name, NULL);
+  AdjustCurrentProcessPrivilege(SE_BACKUP_NAME, false);
+
+  return HRESULT_FROM_WIN32(res);
+}
+
+// restore the key and all of its subkeys and values which are saved into a file
+HRESULT RegKey::Restore(const wchar_t* full_key_name,
+                        const wchar_t* file_name) {
+  ASSERT(full_key_name != NULL);
+  ASSERT(file_name != NULL);
+
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+  if (!h_key) {
+    return E_FAIL;
+  }
+
+  RegKey key;
+  HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_WRITE);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  AdjustCurrentProcessPrivilege(SE_RESTORE_NAME, true);
+  LONG res = ::RegRestoreKey(key.h_key_, file_name, REG_FORCE_RESTORE);
+  AdjustCurrentProcessPrivilege(SE_RESTORE_NAME, false);
+
+  return HRESULT_FROM_WIN32(res);
+}
+
+// check if the current key has the specified subkey
+bool RegKey::HasSubkey(const wchar_t* key_name) const {
+  ASSERT(key_name != NULL);
+
+  RegKey key;
+  HRESULT hr = key.Open(h_key_, key_name, KEY_READ);
+  key.Close();
+  return hr == S_OK;
+}
+
+// static flush key
+HRESULT RegKey::FlushKey(const wchar_t* full_key_name) {
+  ASSERT(full_key_name != NULL);
+
+  HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+  // get the root HKEY
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+
+  if (h_key != NULL) {
+    LONG res = ::RegFlushKey(h_key);
+    hr = HRESULT_FROM_WIN32(res);
+  }
+  return hr;
+}
+
+// static SET helper
+HRESULT RegKey::SetValueStaticHelper(const wchar_t* full_key_name,
+                                     const wchar_t* value_name,
+                                     DWORD type,
+                                     LPVOID value,
+                                     DWORD byte_count) {
+  ASSERT(full_key_name != NULL);
+
+  HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+  // get the root HKEY
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+
+  if (h_key != NULL) {
+    RegKey key;
+    hr = key.Create(h_key, key_name.c_str());
+    if (hr == S_OK) {
+      switch (type) {
+        case REG_DWORD:
+          hr = key.SetValue(value_name, *(static_cast<DWORD*>(value)));
+          break;
+        case REG_QWORD:
+          hr = key.SetValue(value_name, *(static_cast<DWORD64*>(value)));
+          break;
+        case REG_SZ:
+          hr = key.SetValue(value_name, static_cast<const wchar_t*>(value));
+          break;
+        case REG_BINARY:
+          hr = key.SetValue(value_name, static_cast<const uint8*>(value),
+                            byte_count);
+          break;
+        case REG_MULTI_SZ:
+          hr = key.SetValue(value_name, static_cast<const uint8*>(value),
+                            byte_count, type);
+          break;
+        default:
+          ASSERT(false);
+          hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
+          break;
+      }
+      // close the key after writing
+      HRESULT temp_hr = key.Close();
+      if (hr == S_OK) {
+        hr = temp_hr;
+      }
+    }
+  }
+  return hr;
+}
+
+// static GET helper
+HRESULT RegKey::GetValueStaticHelper(const wchar_t* full_key_name,
+                                     const wchar_t* value_name,
+                                     DWORD type,
+                                     LPVOID value,
+                                     DWORD* byte_count) {
+  ASSERT(full_key_name != NULL);
+
+  HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+  // get the root HKEY
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+
+  if (h_key != NULL) {
+    RegKey key;
+    hr = key.Open(h_key, key_name.c_str(), KEY_READ);
+    if (hr == S_OK) {
+      switch (type) {
+        case REG_DWORD:
+          hr = key.GetValue(value_name, reinterpret_cast<DWORD*>(value));
+          break;
+        case REG_QWORD:
+          hr = key.GetValue(value_name, reinterpret_cast<DWORD64*>(value));
+          break;
+        case REG_SZ:
+          hr = key.GetValue(value_name, reinterpret_cast<wchar_t**>(value));
+          break;
+        case REG_MULTI_SZ:
+          hr = key.GetValue(value_name, reinterpret_cast<
+                                            std::vector<std::wstring>*>(value));
+          break;
+        case REG_BINARY:
+          hr = key.GetValue(value_name, reinterpret_cast<uint8**>(value),
+                            byte_count);
+          break;
+        default:
+          ASSERT(false);
+          hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
+          break;
+      }
+      // close the key after writing
+      HRESULT temp_hr = key.Close();
+      if (hr == S_OK) {
+        hr = temp_hr;
+      }
+    }
+  }
+  return hr;
+}
+
+// GET helper
+HRESULT RegKey::GetValueHelper(const wchar_t* value_name,
+                               DWORD* type,
+                               uint8** value,
+                               DWORD* byte_count) const {
+  ASSERT(byte_count != NULL);
+  ASSERT(value != NULL);
+  ASSERT(type != NULL);
+
+  // init return buffer
+  *value = NULL;
+
+  // get the size of the return data buffer
+  LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, type, NULL, byte_count);
+  HRESULT hr = HRESULT_FROM_WIN32(res);
+
+  if (hr == S_OK) {
+    // if the value length is 0, nothing to do
+    if (*byte_count != 0) {
+      // allocate the buffer
+      *value = new byte[*byte_count];
+      ASSERT(*value != NULL);
+
+      // make the call again to get the data
+      res = ::SHQueryValueEx(h_key_, value_name, NULL,
+                             type, *value, byte_count);
+      hr = HRESULT_FROM_WIN32(res);
+      ASSERT(hr == S_OK);
+    }
+  }
+  return hr;
+}
+
+// Int32 Get
+HRESULT RegKey::GetValue(const wchar_t* value_name, DWORD* value) const {
+  ASSERT(value != NULL);
+
+  DWORD type = 0;
+  DWORD byte_count = sizeof(DWORD);
+  LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, &type,
+                              value, &byte_count);
+  HRESULT hr = HRESULT_FROM_WIN32(res);
+  ASSERT((hr != S_OK) || (type == REG_DWORD));
+  ASSERT((hr != S_OK) || (byte_count == sizeof(DWORD)));
+  return hr;
+}
+
+// Int64 Get
+HRESULT RegKey::GetValue(const wchar_t* value_name, DWORD64* value) const {
+  ASSERT(value != NULL);
+
+  DWORD type = 0;
+  DWORD byte_count = sizeof(DWORD64);
+  LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, &type,
+                              value, &byte_count);
+  HRESULT hr = HRESULT_FROM_WIN32(res);
+  ASSERT((hr != S_OK) || (type == REG_QWORD));
+  ASSERT((hr != S_OK) || (byte_count == sizeof(DWORD64)));
+  return hr;
+}
+
+// String Get
+HRESULT RegKey::GetValue(const wchar_t* value_name, wchar_t** value) const {
+  ASSERT(value != NULL);
+
+  DWORD byte_count = 0;
+  DWORD type = 0;
+
+  // first get the size of the string buffer
+  LONG res = ::SHQueryValueEx(h_key_, value_name, NULL,
+                              &type, NULL, &byte_count);
+  HRESULT hr = HRESULT_FROM_WIN32(res);
+
+  if (hr == S_OK) {
+    // allocate room for the string and a terminating \0
+    *value = new wchar_t[(byte_count / sizeof(wchar_t)) + 1];
+
+    if ((*value) != NULL) {
+      if (byte_count != 0) {
+        // make the call again
+        res = ::SHQueryValueEx(h_key_, value_name, NULL, &type,
+                               *value, &byte_count);
+        hr = HRESULT_FROM_WIN32(res);
+      } else {
+        (*value)[0] = L'\0';
+      }
+
+      ASSERT((hr != S_OK) || (type == REG_SZ) ||
+             (type == REG_MULTI_SZ) || (type == REG_EXPAND_SZ));
+    } else {
+      hr = E_OUTOFMEMORY;
+    }
+  }
+
+  return hr;
+}
+
+// get a string value
+HRESULT RegKey::GetValue(const wchar_t* value_name, std::wstring* value) const {
+  ASSERT(value != NULL);
+
+  DWORD byte_count = 0;
+  DWORD type = 0;
+
+  // first get the size of the string buffer
+  LONG res = ::SHQueryValueEx(h_key_, value_name, NULL,
+                              &type, NULL, &byte_count);
+  HRESULT hr = HRESULT_FROM_WIN32(res);
+
+  if (hr == S_OK) {
+    if (byte_count != 0) {
+      // Allocate some memory and make the call again
+      value->resize(byte_count / sizeof(wchar_t) + 1);
+      res = ::SHQueryValueEx(h_key_, value_name, NULL, &type,
+                             &value->at(0), &byte_count);
+      hr = HRESULT_FROM_WIN32(res);
+      value->resize(wcslen(value->data()));
+    } else {
+      value->clear();
+    }
+
+    ASSERT((hr != S_OK) || (type == REG_SZ) ||
+           (type == REG_MULTI_SZ) || (type == REG_EXPAND_SZ));
+  }
+
+  return hr;
+}
+
+// convert REG_MULTI_SZ bytes to string array
+HRESULT RegKey::MultiSZBytesToStringArray(const uint8* buffer,
+                                          DWORD byte_count,
+                                          std::vector<std::wstring>* value) {
+  ASSERT(buffer != NULL);
+  ASSERT(value != NULL);
+
+  const wchar_t* data = reinterpret_cast<const wchar_t*>(buffer);
+  DWORD data_len = byte_count / sizeof(wchar_t);
+  value->clear();
+  if (data_len > 1) {
+    // must be terminated by two null characters
+    if (data[data_len - 1] != 0 || data[data_len - 2] != 0) {
+      return E_INVALIDARG;
+    }
+
+    // put null-terminated strings into arrays
+    while (*data) {
+      std::wstring str(data);
+      value->push_back(str);
+      data += str.length() + 1;
+    }
+  }
+  return S_OK;
+}
+
+// get a std::vector<std::wstring> value from REG_MULTI_SZ type
+HRESULT RegKey::GetValue(const wchar_t* value_name,
+                         std::vector<std::wstring>* value) const {
+  ASSERT(value != NULL);
+
+  DWORD byte_count = 0;
+  DWORD type = 0;
+  uint8* buffer = 0;
+
+  // first get the size of the buffer
+  HRESULT hr = GetValueHelper(value_name, &type, &buffer, &byte_count);
+  ASSERT((hr != S_OK) || (type == REG_MULTI_SZ));
+
+  if (SUCCEEDED(hr)) {
+    hr = MultiSZBytesToStringArray(buffer, byte_count, value);
+  }
+
+  return hr;
+}
+
+// Binary data Get
+HRESULT RegKey::GetValue(const wchar_t* value_name,
+                         uint8** value,
+                         DWORD* byte_count) const {
+  ASSERT(byte_count != NULL);
+  ASSERT(value != NULL);
+
+  DWORD type = 0;
+  HRESULT hr = GetValueHelper(value_name, &type, value, byte_count);
+  ASSERT((hr != S_OK) || (type == REG_MULTI_SZ) || (type == REG_BINARY));
+  return hr;
+}
+
+// Raw data get
+HRESULT RegKey::GetValue(const wchar_t* value_name,
+                         uint8** value,
+                         DWORD* byte_count,
+                         DWORD*type) const {
+  ASSERT(type != NULL);
+  ASSERT(byte_count != NULL);
+  ASSERT(value != NULL);
+
+  return GetValueHelper(value_name, type, value, byte_count);
+}
+
+// Int32 set
+HRESULT RegKey::SetValue(const wchar_t* value_name, DWORD value) const {
+  ASSERT(h_key_ != NULL);
+
+  LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_DWORD,
+                             reinterpret_cast<const uint8*>(&value),
+                             sizeof(DWORD));
+  return HRESULT_FROM_WIN32(res);
+}
+
+// Int64 set
+HRESULT RegKey::SetValue(const wchar_t* value_name, DWORD64 value) const {
+  ASSERT(h_key_ != NULL);
+
+  LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_QWORD,
+                             reinterpret_cast<const uint8*>(&value),
+                             sizeof(DWORD64));
+  return HRESULT_FROM_WIN32(res);
+}
+
+// String set
+HRESULT RegKey::SetValue(const wchar_t* value_name,
+                         const wchar_t* value) const {
+  ASSERT(value != NULL);
+  ASSERT(h_key_ != NULL);
+
+  LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_SZ,
+                             reinterpret_cast<const uint8*>(value),
+                             (lstrlen(value) + 1) * sizeof(wchar_t));
+  return HRESULT_FROM_WIN32(res);
+}
+
+// Binary data set
+HRESULT RegKey::SetValue(const wchar_t* value_name,
+                         const uint8* value,
+                         DWORD byte_count) const {
+  ASSERT(h_key_ != NULL);
+
+  // special case - if 'value' is NULL make sure byte_count is zero
+  if (value == NULL) {
+    byte_count = 0;
+  }
+
+  LONG res = ::RegSetValueEx(h_key_, value_name, NULL,
+                             REG_BINARY, value, byte_count);
+  return HRESULT_FROM_WIN32(res);
+}
+
+// Raw data set
+HRESULT RegKey::SetValue(const wchar_t* value_name,
+                         const uint8* value,
+                         DWORD byte_count,
+                         DWORD type) const {
+  ASSERT(value != NULL);
+  ASSERT(h_key_ != NULL);
+
+  LONG res = ::RegSetValueEx(h_key_, value_name, NULL, type, value, byte_count);
+  return HRESULT_FROM_WIN32(res);
+}
+
+bool RegKey::HasKey(const wchar_t* full_key_name) {
+  ASSERT(full_key_name != NULL);
+
+  // get the root HKEY
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+
+  if (h_key != NULL) {
+    RegKey key;
+    HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ);
+    key.Close();
+    return S_OK == hr;
+  }
+  return false;
+}
+
+// static version of HasValue
+bool RegKey::HasValue(const wchar_t* full_key_name, const wchar_t* value_name) {
+  ASSERT(full_key_name != NULL);
+
+  bool has_value = false;
+  // get the root HKEY
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+
+  if (h_key != NULL) {
+    RegKey key;
+    if (key.Open(h_key, key_name.c_str(), KEY_READ) == S_OK) {
+      has_value = key.HasValue(value_name);
+      key.Close();
+    }
+  }
+  return has_value;
+}
+
+HRESULT RegKey::GetValueType(const wchar_t* full_key_name,
+                             const wchar_t* value_name,
+                             DWORD* value_type) {
+  ASSERT(full_key_name != NULL);
+  ASSERT(value_type != NULL);
+
+  *value_type = REG_NONE;
+
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+
+  RegKey key;
+  HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ);
+  if (SUCCEEDED(hr)) {
+    LONG res = ::SHQueryValueEx(key.h_key_, value_name, NULL, value_type,
+                                NULL, NULL);
+    if (res != ERROR_SUCCESS) {
+      hr = HRESULT_FROM_WIN32(res);
+    }
+  }
+
+  return hr;
+}
+
+HRESULT RegKey::DeleteKey(const wchar_t* full_key_name) {
+  ASSERT(full_key_name != NULL);
+
+  return DeleteKey(full_key_name, true);
+}
+
+HRESULT RegKey::DeleteKey(const wchar_t* full_key_name, bool recursively) {
+  ASSERT(full_key_name != NULL);
+
+  // need to open the parent key first
+  // get the root HKEY
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+
+  // get the parent key
+  std::wstring parent_key(GetParentKeyInfo(&key_name));
+
+  RegKey key;
+  HRESULT hr = key.Open(h_key, parent_key.c_str());
+
+  if (hr == S_OK) {
+    hr = recursively ? key.RecurseDeleteSubKey(key_name.c_str())
+                     : key.DeleteSubKey(key_name.c_str());
+  } else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
+             hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) {
+    hr = S_FALSE;
+  }
+
+  key.Close();
+  return hr;
+}
+
+HRESULT RegKey::DeleteValue(const wchar_t* full_key_name,
+                            const wchar_t* value_name) {
+  ASSERT(full_key_name != NULL);
+
+  HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+  // get the root HKEY
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+
+  if (h_key != NULL) {
+    RegKey key;
+    hr = key.Open(h_key, key_name.c_str());
+    if (hr == S_OK) {
+      hr = key.DeleteValue(value_name);
+      key.Close();
+    }
+  }
+  return hr;
+}
+
+HRESULT RegKey::RecurseDeleteSubKey(const wchar_t* key_name) {
+  ASSERT(key_name != NULL);
+
+  RegKey key;
+  HRESULT hr = key.Open(h_key_, key_name);
+
+  if (hr == S_OK) {
+    // enumerate all subkeys of this key and recursivelly delete them
+    FILETIME time = {0};
+    wchar_t key_name_buf[kMaxKeyNameChars] = {0};
+    DWORD key_name_buf_size = kMaxKeyNameChars;
+    while (hr == S_OK &&
+        ::RegEnumKeyEx(key.h_key_, 0, key_name_buf, &key_name_buf_size,
+                       NULL, NULL, NULL,  &time) == ERROR_SUCCESS) {
+      hr = key.RecurseDeleteSubKey(key_name_buf);
+
+      // restore the buffer size
+      key_name_buf_size = kMaxKeyNameChars;
+    }
+    // close the top key
+    key.Close();
+  }
+
+  if (hr == S_OK) {
+    // the key has no more children keys
+    // delete the key and all of its values
+    hr = DeleteSubKey(key_name);
+  }
+
+  return hr;
+}
+
+HKEY RegKey::GetRootKeyInfo(std::wstring* full_key_name) {
+  ASSERT(full_key_name != NULL);
+
+  HKEY h_key = NULL;
+  // get the root HKEY
+  int index = full_key_name->find(L'\\');
+  std::wstring root_key;
+
+  if (index == -1) {
+    root_key = *full_key_name;
+    *full_key_name = L"";
+  } else {
+    root_key = full_key_name->substr(0, index);
+    *full_key_name = full_key_name->substr(index + 1,
+                                           full_key_name->length() - index - 1);
+  }
+
+  for (std::wstring::iterator iter = root_key.begin();
+       iter != root_key.end(); ++iter) {
+    *iter = toupper(*iter);
+  }
+
+  if (!root_key.compare(L"HKLM") ||
+      !root_key.compare(L"HKEY_LOCAL_MACHINE")) {
+    h_key = HKEY_LOCAL_MACHINE;
+  } else if (!root_key.compare(L"HKCU") ||
+             !root_key.compare(L"HKEY_CURRENT_USER")) {
+    h_key = HKEY_CURRENT_USER;
+  } else if (!root_key.compare(L"HKU") ||
+             !root_key.compare(L"HKEY_USERS")) {
+    h_key = HKEY_USERS;
+  } else if (!root_key.compare(L"HKCR") ||
+             !root_key.compare(L"HKEY_CLASSES_ROOT")) {
+    h_key = HKEY_CLASSES_ROOT;
+  }
+
+  return h_key;
+}
+
+
+// Returns true if this key name is 'safe' for deletion
+// (doesn't specify a key root)
+bool RegKey::SafeKeyNameForDeletion(const wchar_t* key_name) {
+  ASSERT(key_name != NULL);
+  std::wstring key(key_name);
+
+  HKEY root_key = GetRootKeyInfo(&key);
+
+  if (!root_key) {
+    key = key_name;
+  }
+  if (key.empty()) {
+    return false;
+  }
+  bool found_subkey = false, backslash_found = false;
+  for (size_t i = 0 ; i < key.length() ; ++i) {
+    if (key[i] == L'\\') {
+      backslash_found = true;
+    } else if (backslash_found) {
+      found_subkey = true;
+      break;
+    }
+  }
+  return (root_key == HKEY_USERS) ? found_subkey : true;
+}
+
+std::wstring RegKey::GetParentKeyInfo(std::wstring* key_name) {
+  ASSERT(key_name != NULL);
+
+  // get the parent key
+  int index = key_name->rfind(L'\\');
+  std::wstring parent_key;
+  if (index == -1) {
+    parent_key = L"";
+  } else {
+    parent_key = key_name->substr(0, index);
+    *key_name = key_name->substr(index + 1, key_name->length() - index - 1);
+  }
+
+  return parent_key;
+}
+
+// get the number of values for this key
+uint32 RegKey::GetValueCount() {
+  DWORD num_values = 0;
+
+  LONG res = ::RegQueryInfoKey(
+        h_key_,                  // key handle
+        NULL,                    // buffer for class name
+        NULL,                    // size of class string
+        NULL,                    // reserved
+        NULL,                    // number of subkeys
+        NULL,                    // longest subkey size
+        NULL,                    // longest class string
+        &num_values,             // number of values for this key
+        NULL,                    // longest value name
+        NULL,                    // longest value data
+        NULL,                    // security descriptor
+        NULL);                   // last write time
+
+  ASSERT(res == ERROR_SUCCESS);
+  return num_values;
+}
+
+// Enumerators for the value_names for this key
+
+// Called to get the value name for the given value name index
+// Use GetValueCount() to get the total value_name count for this key
+// Returns failure if no key at the specified index
+HRESULT RegKey::GetValueNameAt(int index, std::wstring* value_name,
+                               DWORD* type) {
+  ASSERT(value_name != NULL);
+
+  LONG res = ERROR_SUCCESS;
+  wchar_t value_name_buf[kMaxValueNameChars] = {0};
+  DWORD value_name_buf_size = kMaxValueNameChars;
+  res = ::RegEnumValue(h_key_, index, value_name_buf, &value_name_buf_size,
+                       NULL, type, NULL, NULL);
+
+  if (res == ERROR_SUCCESS) {
+    value_name->assign(value_name_buf);
+  }
+
+  return HRESULT_FROM_WIN32(res);
+}
+
+uint32 RegKey::GetSubkeyCount() {
+  // number of values for key
+  DWORD num_subkeys = 0;
+
+  LONG res = ::RegQueryInfoKey(
+    h_key_,                  // key handle
+    NULL,                    // buffer for class name
+    NULL,                    // size of class string
+    NULL,                    // reserved
+    &num_subkeys,            // number of subkeys
+    NULL,                    // longest subkey size
+    NULL,                    // longest class string
+    NULL,                    // number of values for this key
+    NULL,                    // longest value name
+    NULL,                    // longest value data
+    NULL,                    // security descriptor
+    NULL);                   // last write time
+
+  ASSERT(res == ERROR_SUCCESS);
+  return num_subkeys;
+}
+
+HRESULT RegKey::GetSubkeyNameAt(int index, std::wstring* key_name) {
+  ASSERT(key_name != NULL);
+
+  LONG res = ERROR_SUCCESS;
+  wchar_t key_name_buf[kMaxKeyNameChars] = {0};
+  DWORD key_name_buf_size = kMaxKeyNameChars;
+
+  res = ::RegEnumKeyEx(h_key_, index, key_name_buf, &key_name_buf_size,
+                       NULL, NULL, NULL, NULL);
+
+  if (res == ERROR_SUCCESS) {
+    key_name->assign(key_name_buf);
+  }
+
+  return HRESULT_FROM_WIN32(res);
+}
+
+// Is the key empty: having no sub-keys and values
+bool RegKey::IsKeyEmpty(const wchar_t* full_key_name) {
+  ASSERT(full_key_name != NULL);
+
+  bool is_empty = true;
+
+  // Get the root HKEY
+  std::wstring key_name(full_key_name);
+  HKEY h_key = GetRootKeyInfo(&key_name);
+
+  // Open the key to check
+  if (h_key != NULL) {
+    RegKey key;
+    HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ);
+    if (SUCCEEDED(hr)) {
+      is_empty = key.GetSubkeyCount() == 0 && key.GetValueCount() == 0;
+      key.Close();
+    }
+  }
+
+  return is_empty;
+}
+
+bool AdjustCurrentProcessPrivilege(const TCHAR* privilege, bool to_enable) {
+  ASSERT(privilege != NULL);
+
+  bool ret = false;
+  HANDLE token;
+  if (::OpenProcessToken(::GetCurrentProcess(),
+                         TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) {
+    LUID luid;
+    memset(&luid, 0, sizeof(luid));
+    if (::LookupPrivilegeValue(NULL, privilege, &luid)) {
+      TOKEN_PRIVILEGES privs;
+      privs.PrivilegeCount = 1;
+      privs.Privileges[0].Luid = luid;
+      privs.Privileges[0].Attributes = to_enable ? SE_PRIVILEGE_ENABLED : 0;
+      if (::AdjustTokenPrivileges(token, FALSE, &privs, 0, NULL, 0)) {
+        ret = true;
+      } else {
+        LOG_GLE(LS_ERROR) << "AdjustTokenPrivileges failed";
+      }
+    } else {
+      LOG_GLE(LS_ERROR) << "LookupPrivilegeValue failed";
+    }
+    CloseHandle(token);
+  } else {
+    LOG_GLE(LS_ERROR) << "OpenProcessToken(GetCurrentProcess) failed";
+  }
+
+  return ret;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32regkey.h b/talk/base/win32regkey.h
new file mode 100644
index 0000000..9f01ce1
--- /dev/null
+++ b/talk/base/win32regkey.h
@@ -0,0 +1,354 @@
+/*
+ * libjingle
+ * Copyright 2003-2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Registry configuration wrappers class
+//
+// Offers static functions for convenient
+// fast access for individual values
+//
+// Also provides a wrapper class for efficient
+// batch operations on values of a given registry key.
+//
+
+#ifndef TALK_BASE_WIN32REGKEY_H_
+#define TALK_BASE_WIN32REGKEY_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/win32.h"
+
+namespace talk_base {
+
+// maximum sizes registry key and value names
+const int kMaxKeyNameChars = 255 + 1;
+const int kMaxValueNameChars = 16383 + 1;
+
+class RegKey {
+ public:
+  // constructor
+  RegKey();
+
+  // destructor
+  ~RegKey();
+
+  // create a reg key
+  HRESULT Create(HKEY parent_key, const wchar_t* key_name);
+
+  HRESULT Create(HKEY parent_key,
+                 const wchar_t* key_name,
+                 wchar_t* reg_class,
+                 DWORD options,
+                 REGSAM sam_desired,
+                 LPSECURITY_ATTRIBUTES lp_sec_attr,
+                 LPDWORD lp_disposition);
+
+  // open an existing reg key
+  HRESULT Open(HKEY parent_key, const wchar_t* key_name);
+
+  HRESULT Open(HKEY parent_key, const wchar_t* key_name, REGSAM sam_desired);
+
+  // close this reg key
+  HRESULT Close();
+
+  // check if the key has a specified value
+  bool HasValue(const wchar_t* value_name) const;
+
+  // get the number of values for this key
+  uint32 GetValueCount();
+
+  // Called to get the value name for the given value name index
+  // Use GetValueCount() to get the total value_name count for this key
+  // Returns failure if no key at the specified index
+  // If you modify the key while enumerating, the indexes will be out of order.
+  // Since the index order is not guaranteed, you need to reset your counting
+  // loop.
+  // 'type' refers to REG_DWORD, REG_QWORD, etc..
+  // 'type' can be NULL if not interested in the value type
+  HRESULT GetValueNameAt(int index, std::wstring* value_name, DWORD* type);
+
+  // check if the current key has the specified subkey
+  bool HasSubkey(const wchar_t* key_name) const;
+
+  // get the number of subkeys for this key
+  uint32 GetSubkeyCount();
+
+  // Called to get the key name for the given key index
+  // Use GetSubkeyCount() to get the total count for this key
+  // Returns failure if no key at the specified index
+  // If you modify the key while enumerating, the indexes will be out of order.
+  // Since the index order is not guaranteed, you need to reset your counting
+  // loop.
+  HRESULT GetSubkeyNameAt(int index, std::wstring* key_name);
+
+  // SETTERS
+
+  // set an int32 value - use when reading multiple values from a key
+  HRESULT SetValue(const wchar_t* value_name, DWORD value) const;
+
+  // set an int64 value
+  HRESULT SetValue(const wchar_t* value_name, DWORD64 value) const;
+
+  // set a string value
+  HRESULT SetValue(const wchar_t* value_name, const wchar_t* value) const;
+
+  // set binary data
+  HRESULT SetValue(const wchar_t* value_name,
+                   const uint8* value,
+                   DWORD byte_count) const;
+
+  // set raw data, including type
+  HRESULT SetValue(const wchar_t* value_name,
+                   const uint8* value,
+                   DWORD byte_count,
+                   DWORD type) const;
+
+  // GETTERS
+
+  // get an int32 value
+  HRESULT GetValue(const wchar_t* value_name, DWORD* value) const;
+
+  // get an int64 value
+  HRESULT GetValue(const wchar_t* value_name, DWORD64* value) const;
+
+  // get a string value - the caller must free the return buffer
+  HRESULT GetValue(const wchar_t* value_name, wchar_t** value) const;
+
+  // get a string value
+  HRESULT GetValue(const wchar_t* value_name, std::wstring* value) const;
+
+  // get a std::vector<std::wstring> value from REG_MULTI_SZ type
+  HRESULT GetValue(const wchar_t* value_name,
+                   std::vector<std::wstring>* value) const;
+
+  // get binary data - the caller must free the return buffer
+  HRESULT GetValue(const wchar_t* value_name,
+                   uint8** value,
+                   DWORD* byte_count) const;
+
+  // get raw data, including type - the caller must free the return buffer
+  HRESULT GetValue(const wchar_t* value_name,
+                   uint8** value,
+                   DWORD* byte_count,
+                   DWORD* type) const;
+
+  // STATIC VERSIONS
+
+  // flush
+  static HRESULT FlushKey(const wchar_t* full_key_name);
+
+  // check if a key exists
+  static bool HasKey(const wchar_t* full_key_name);
+
+  // check if the key has a specified value
+  static bool HasValue(const wchar_t* full_key_name, const wchar_t* value_name);
+
+  // SETTERS
+
+  // STATIC int32 set
+  static HRESULT SetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          DWORD value);
+
+  // STATIC int64 set
+  static HRESULT SetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          DWORD64 value);
+
+  // STATIC float set
+  static HRESULT SetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          float value);
+
+  // STATIC double set
+  static HRESULT SetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          double value);
+
+  // STATIC string set
+  static HRESULT SetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          const wchar_t* value);
+
+  // STATIC binary data set
+  static HRESULT SetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          const uint8* value,
+                          DWORD byte_count);
+
+  // STATIC multi-string set
+  static HRESULT SetValueMultiSZ(const wchar_t* full_key_name,
+                                 const TCHAR* value_name,
+                                 const uint8* value,
+                                 DWORD byte_count);
+
+  // GETTERS
+
+  // STATIC int32 get
+  static HRESULT GetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          DWORD* value);
+
+  // STATIC int64 get
+  //
+  // Note: if you are using time64 you should
+  // likely use GetLimitedTimeValue (util.h) instead of this method.
+  static HRESULT GetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          DWORD64* value);
+
+  // STATIC float get
+  static HRESULT GetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          float* value);
+
+  // STATIC double get
+  static HRESULT GetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          double* value);
+
+  // STATIC string get
+  // Note: the caller must free the return buffer for wchar_t* version
+  static HRESULT GetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          wchar_t** value);
+  static HRESULT GetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          std::wstring* value);
+
+  // STATIC REG_MULTI_SZ get
+  static HRESULT GetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          std::vector<std::wstring>* value);
+
+  // STATIC get binary data - the caller must free the return buffer
+  static HRESULT GetValue(const wchar_t* full_key_name,
+                          const wchar_t* value_name,
+                          uint8** value,
+                          DWORD* byte_count);
+
+  // Get type of a registry value
+  static HRESULT GetValueType(const wchar_t* full_key_name,
+                              const wchar_t* value_name,
+                              DWORD* value_type);
+
+  // delete a subkey of the current key (with no subkeys)
+  HRESULT DeleteSubKey(const wchar_t* key_name);
+
+  // recursively delete a sub key of the current key (and all its subkeys)
+  HRESULT RecurseDeleteSubKey(const wchar_t* key_name);
+
+  // STATIC version of delete key - handles nested keys also
+  // delete a key and all its sub-keys recursively
+  // Returns S_FALSE if key didn't exist, S_OK if deletion was successful,
+  // and failure otherwise.
+  static HRESULT DeleteKey(const wchar_t* full_key_name);
+
+  // STATIC version of delete key
+  // delete a key recursively or non-recursively
+  // Returns S_FALSE if key didn't exist, S_OK if deletion was successful,
+  // and failure otherwise.
+  static HRESULT DeleteKey(const wchar_t* full_key_name, bool recursive);
+
+  // delete the specified value
+  HRESULT DeleteValue(const wchar_t* value_name);
+
+  // STATIC version of delete value
+  // Returns S_FALSE if key didn't exist, S_OK if deletion was successful,
+  // and failure otherwise.
+  static HRESULT DeleteValue(const wchar_t* full_key_name,
+                             const wchar_t* value_name);
+
+  // Peek inside (use a RegKey as a smart wrapper around a registry handle)
+  HKEY key() { return h_key_; }
+
+  // helper function to get the HKEY and the root key from a string
+  // modifies the argument in place and returns the key name
+  // e.g. HKLM\\Software\\Google\... returns HKLM, "Software\\Google\..."
+  // Necessary for the static versions that use the full name of the reg key
+  static HKEY GetRootKeyInfo(std::wstring* full_key_name);
+
+  // Returns true if this key name is 'safe' for deletion (doesn't specify a key
+  // root)
+  static bool SafeKeyNameForDeletion(const wchar_t* key_name);
+
+  // save the key and all of its subkeys and values to a file
+  static HRESULT Save(const wchar_t* full_key_name, const wchar_t* file_name);
+
+  // restore the key and all of its subkeys and values which are saved into a
+  // file
+  static HRESULT Restore(const wchar_t* full_key_name,
+                         const wchar_t* file_name);
+
+  // Is the key empty: having no sub-keys and values
+  static bool IsKeyEmpty(const wchar_t* full_key_name);
+
+ private:
+
+  // helper function to get any value from the registry
+  // used when the size of the data is unknown
+  HRESULT GetValueHelper(const wchar_t* value_name,
+                         DWORD* type, uint8** value,
+                         DWORD* byte_count) const;
+
+  // helper function to get the parent key name and the subkey from a string
+  // modifies the argument in place and returns the key name
+  // Necessary for the static versions that use the full name of the reg key
+  static std::wstring GetParentKeyInfo(std::wstring* key_name);
+
+  // common SET Helper for the static case
+  static HRESULT SetValueStaticHelper(const wchar_t* full_key_name,
+                                      const wchar_t* value_name,
+                                      DWORD type,
+                                      LPVOID value,
+                                      DWORD byte_count = 0);
+
+  // common GET Helper for the static case
+  static HRESULT GetValueStaticHelper(const wchar_t* full_key_name,
+                                      const wchar_t* value_name,
+                                      DWORD type,
+                                      LPVOID value,
+                                      DWORD* byte_count = NULL);
+
+  // convert REG_MULTI_SZ bytes to string array
+  static HRESULT MultiSZBytesToStringArray(const uint8* buffer,
+                                           DWORD byte_count,
+                                           std::vector<std::wstring>* value);
+
+  // the HKEY for the current key
+  HKEY h_key_;
+
+  // for unittest
+  friend void RegKeyHelperFunctionsTest();
+
+  DISALLOW_EVIL_CONSTRUCTORS(RegKey);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WIN32REGKEY_H_
diff --git a/talk/base/win32regkey_unittest.cc b/talk/base/win32regkey_unittest.cc
new file mode 100644
index 0000000..1dd8fe4
--- /dev/null
+++ b/talk/base/win32regkey_unittest.cc
@@ -0,0 +1,607 @@
+/*
+ * libjingle
+ * Copyright 2003-2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Unittest for registry access API
+
+#include "talk/base/gunit.h"
+#include "talk/base/common.h"
+#include "talk/base/win32regkey.h"
+
+namespace talk_base {
+
+#ifndef EXPECT_SUCCEEDED
+#define EXPECT_SUCCEEDED(x)  EXPECT_TRUE(SUCCEEDED(x))
+#endif
+
+#ifndef EXPECT_FAILED
+#define EXPECT_FAILED(x)  EXPECT_TRUE(FAILED(x))
+#endif
+
+#define kBaseKey           L"Software\\Google\\__TEST"
+#define kSubkeyName        L"subkey_test"
+
+const wchar_t kRkey1[] = kBaseKey;
+const wchar_t kRkey1SubkeyName[] = kSubkeyName;
+const wchar_t kRkey1Subkey[] = kBaseKey L"\\" kSubkeyName;
+const wchar_t kFullRkey1[] = L"HKCU\\" kBaseKey;
+const wchar_t kFullRkey1Subkey[] = L"HKCU\\" kBaseKey L"\\" kSubkeyName;
+
+const wchar_t kValNameInt[] = L"Int32 Value";
+const DWORD kIntVal = 20;
+const DWORD kIntVal2 = 30;
+
+const wchar_t kValNameInt64[] = L"Int64 Value";
+const DWORD64 kIntVal64 = 119600064000000000uI64;
+
+const wchar_t kValNameFloat[] = L"Float Value";
+const float kFloatVal = 12.3456789f;
+
+const wchar_t kValNameDouble[] = L"Double Value";
+const double kDoubleVal = 98.7654321;
+
+const wchar_t kValNameStr[] = L"Str Value";
+const wchar_t kStrVal[] = L"Some string data 1";
+const wchar_t kStrVal2[] = L"Some string data 2";
+
+const wchar_t kValNameBinary[] = L"Binary Value";
+const char kBinaryVal[] = "Some binary data abcdefghi 1";
+const char kBinaryVal2[] = "Some binary data abcdefghi 2";
+
+const wchar_t kValNameMultiStr[] = L"MultiStr Value";
+const wchar_t kMultiSZ[] = L"abc\0def\0P12345\0";
+const wchar_t kEmptyMultiSZ[] = L"";
+const wchar_t kInvalidMultiSZ[] = {L'6', L'7', L'8'};
+
+// friend function of RegKey
+void RegKeyHelperFunctionsTest() {
+  // Try out some dud values
+  std::wstring temp_key = L"";
+  EXPECT_TRUE(RegKey::GetRootKeyInfo(&temp_key) == NULL);
+  EXPECT_STREQ(temp_key.c_str(), L"");
+
+  temp_key = L"a";
+  EXPECT_TRUE(RegKey::GetRootKeyInfo(&temp_key) == NULL);
+  EXPECT_STREQ(temp_key.c_str(), L"");
+
+  // The basics
+  temp_key = L"HKLM\\a";
+  EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_LOCAL_MACHINE);
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  temp_key = L"HKEY_LOCAL_MACHINE\\a";
+  EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_LOCAL_MACHINE);
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  temp_key = L"HKCU\\a";
+  EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CURRENT_USER);
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  temp_key = L"HKEY_CURRENT_USER\\a";
+  EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CURRENT_USER);
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  temp_key = L"HKU\\a";
+  EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_USERS);
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  temp_key = L"HKEY_USERS\\a";
+  EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_USERS);
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  temp_key = L"HKCR\\a";
+  EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT);
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  temp_key = L"HKEY_CLASSES_ROOT\\a";
+  EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT);
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  // Make sure it is case insensitive
+  temp_key = L"hkcr\\a";
+  EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT);
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  temp_key = L"hkey_CLASSES_ROOT\\a";
+  EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT);
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  //
+  // Test RegKey::GetParentKeyInfo
+  //
+
+  // dud cases
+  temp_key = L"";
+  EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L"");
+  EXPECT_STREQ(temp_key.c_str(), L"");
+
+  temp_key = L"a";
+  EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L"");
+  EXPECT_STREQ(temp_key.c_str(), L"a");
+
+  temp_key = L"a\\b";
+  EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L"a");
+  EXPECT_STREQ(temp_key.c_str(), L"b");
+
+  temp_key = L"\\b";
+  EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L"");
+  EXPECT_STREQ(temp_key.c_str(), L"b");
+
+  // Some regular cases
+  temp_key = L"HKEY_CLASSES_ROOT\\moon";
+  EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(),
+               L"HKEY_CLASSES_ROOT");
+  EXPECT_STREQ(temp_key.c_str(), L"moon");
+
+  temp_key = L"HKEY_CLASSES_ROOT\\moon\\doggy";
+  EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(),
+               L"HKEY_CLASSES_ROOT\\moon");
+  EXPECT_STREQ(temp_key.c_str(), L"doggy");
+
+  //
+  // Test MultiSZBytesToStringArray
+  //
+
+  std::vector<std::wstring> result;
+  EXPECT_SUCCEEDED(RegKey::MultiSZBytesToStringArray(
+      reinterpret_cast<const uint8*>(kMultiSZ), sizeof(kMultiSZ), &result));
+  EXPECT_EQ(result.size(), 3);
+  EXPECT_STREQ(result[0].c_str(), L"abc");
+  EXPECT_STREQ(result[1].c_str(), L"def");
+  EXPECT_STREQ(result[2].c_str(), L"P12345");
+
+  EXPECT_SUCCEEDED(RegKey::MultiSZBytesToStringArray(
+      reinterpret_cast<const uint8*>(kEmptyMultiSZ),
+      sizeof(kEmptyMultiSZ), &result));
+  EXPECT_EQ(result.size(), 0);
+  EXPECT_FALSE(SUCCEEDED(RegKey::MultiSZBytesToStringArray(
+      reinterpret_cast<const uint8*>(kInvalidMultiSZ),
+      sizeof(kInvalidMultiSZ), &result)));
+}
+
+TEST(RegKeyTest, RegKeyHelperFunctionsTest) {
+  RegKeyHelperFunctionsTest();
+}
+
+TEST(RegKeyTest, RegKeyNonStaticFunctionsTest) {
+  DWORD int_val = 0;
+  DWORD64 int64_val = 0;
+  wchar_t* str_val = NULL;
+  uint8* binary_val = NULL;
+  DWORD uint8_count = 0;
+
+  // Just in case...
+  // make sure the no test key residue is left from previous aborted runs
+  RegKey::DeleteKey(kFullRkey1);
+
+  // initial state
+  RegKey r_key;
+  EXPECT_TRUE(r_key.key() == NULL);
+
+  // create a reg key
+  EXPECT_SUCCEEDED(r_key.Create(HKEY_CURRENT_USER, kRkey1));
+
+  // do the create twice - it should return the already created one
+  EXPECT_SUCCEEDED(r_key.Create(HKEY_CURRENT_USER, kRkey1));
+
+  // now do an open - should work just fine
+  EXPECT_SUCCEEDED(r_key.Open(HKEY_CURRENT_USER, kRkey1));
+
+  // get an in-existent value
+  EXPECT_EQ(r_key.GetValue(kValNameInt, &int_val),
+            HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
+
+  // set and get some values
+
+  // set an INT 32
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal));
+
+  // check that the value exists
+  EXPECT_TRUE(r_key.HasValue(kValNameInt));
+
+  // read it back
+  EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt, &int_val));
+  EXPECT_EQ(int_val, kIntVal);
+
+  // set it again!
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal2));
+
+  // read it again
+  EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt, &int_val));
+  EXPECT_EQ(int_val, kIntVal2);
+
+  // delete the value
+  EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameInt));
+
+  // check that the value is gone
+  EXPECT_FALSE(r_key.HasValue(kValNameInt));
+
+  // set an INT 64
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt64, kIntVal64));
+
+  // check that the value exists
+  EXPECT_TRUE(r_key.HasValue(kValNameInt64));
+
+  // read it back
+  EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt64, &int64_val));
+  EXPECT_EQ(int64_val, kIntVal64);
+
+  // delete the value
+  EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameInt64));
+
+  // check that the value is gone
+  EXPECT_FALSE(r_key.HasValue(kValNameInt64));
+
+  // set a string
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal));
+
+  // check that the value exists
+  EXPECT_TRUE(r_key.HasValue(kValNameStr));
+
+  // read it back
+  EXPECT_SUCCEEDED(r_key.GetValue(kValNameStr, &str_val));
+  EXPECT_TRUE(lstrcmp(str_val, kStrVal) == 0);
+  delete[] str_val;
+
+  // set it again
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal2));
+
+  // read it again
+  EXPECT_SUCCEEDED(r_key.GetValue(kValNameStr, &str_val));
+  EXPECT_TRUE(lstrcmp(str_val, kStrVal2) == 0);
+  delete[] str_val;
+
+  // delete the value
+  EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameStr));
+
+  // check that the value is gone
+  EXPECT_FALSE(r_key.HasValue(kValNameInt));
+
+  // set a binary value
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary,
+      reinterpret_cast<const uint8*>(kBinaryVal), sizeof(kBinaryVal) - 1));
+
+  // check that the value exists
+  EXPECT_TRUE(r_key.HasValue(kValNameBinary));
+
+  // read it back
+  EXPECT_SUCCEEDED(r_key.GetValue(kValNameBinary, &binary_val, &uint8_count));
+  EXPECT_TRUE(memcmp(binary_val, kBinaryVal, sizeof(kBinaryVal) - 1) == 0);
+  delete[] binary_val;
+
+  // set it again
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary,
+      reinterpret_cast<const uint8*>(kBinaryVal2), sizeof(kBinaryVal) - 1));
+
+  // read it again
+  EXPECT_SUCCEEDED(r_key.GetValue(kValNameBinary, &binary_val, &uint8_count));
+  EXPECT_TRUE(memcmp(binary_val, kBinaryVal2, sizeof(kBinaryVal2) - 1) == 0);
+  delete[] binary_val;
+
+  // delete the value
+  EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameBinary));
+
+  // check that the value is gone
+  EXPECT_FALSE(r_key.HasValue(kValNameBinary));
+
+  // set some values and check the total count
+
+  // set an INT 32
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal));
+
+  // set an INT 64
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt64, kIntVal64));
+
+  // set a string
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal));
+
+  // set a binary value
+  EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary,
+      reinterpret_cast<const uint8*>(kBinaryVal), sizeof(kBinaryVal) - 1));
+
+  // get the value count
+  uint32 value_count = r_key.GetValueCount();
+  EXPECT_EQ(value_count, 4);
+
+  // check the value names
+  std::wstring value_name;
+  DWORD type = 0;
+
+  EXPECT_SUCCEEDED(r_key.GetValueNameAt(0, &value_name, &type));
+  EXPECT_STREQ(value_name.c_str(), kValNameInt);
+  EXPECT_EQ(type, REG_DWORD);
+
+  EXPECT_SUCCEEDED(r_key.GetValueNameAt(1, &value_name, &type));
+  EXPECT_STREQ(value_name.c_str(), kValNameInt64);
+  EXPECT_EQ(type, REG_QWORD);
+
+  EXPECT_SUCCEEDED(r_key.GetValueNameAt(2, &value_name, &type));
+  EXPECT_STREQ(value_name.c_str(), kValNameStr);
+  EXPECT_EQ(type, REG_SZ);
+
+  EXPECT_SUCCEEDED(r_key.GetValueNameAt(3, &value_name, &type));
+  EXPECT_STREQ(value_name.c_str(), kValNameBinary);
+  EXPECT_EQ(type, REG_BINARY);
+
+  // check that there are no more values
+  EXPECT_FAILED(r_key.GetValueNameAt(4, &value_name, &type));
+
+  uint32 subkey_count = r_key.GetSubkeyCount();
+  EXPECT_EQ(subkey_count, 0);
+
+  // now create a subkey and make sure we can get the name
+  RegKey temp_key;
+  EXPECT_SUCCEEDED(temp_key.Create(HKEY_CURRENT_USER, kRkey1Subkey));
+
+  // check the subkey exists
+  EXPECT_TRUE(r_key.HasSubkey(kRkey1SubkeyName));
+
+  // check the name
+  EXPECT_EQ(r_key.GetSubkeyCount(), 1);
+
+  std::wstring subkey_name;
+  EXPECT_SUCCEEDED(r_key.GetSubkeyNameAt(0, &subkey_name));
+  EXPECT_STREQ(subkey_name.c_str(), kRkey1SubkeyName);
+
+  // delete the key
+  EXPECT_SUCCEEDED(r_key.DeleteSubKey(kRkey1));
+
+  // close this key
+  EXPECT_SUCCEEDED(r_key.Close());
+
+  // whack the whole key
+  EXPECT_SUCCEEDED(RegKey::DeleteKey(kFullRkey1));
+}
+
+TEST(RegKeyTest, RegKeyStaticFunctionsTest) {
+  DWORD int_val = 0;
+  DWORD64 int64_val = 0;
+  float float_val = 0;
+  double double_val = 0;
+  wchar_t* str_val = NULL;
+  std::wstring wstr_val;
+  uint8* binary_val = NULL;
+  DWORD uint8_count = 0;
+
+  // Just in case...
+  // make sure the no test key residue is left from previous aborted runs
+  RegKey::DeleteKey(kFullRkey1);
+
+  // get an in-existent value from an un-existent key
+  EXPECT_EQ(RegKey::GetValue(kFullRkey1, kValNameInt, &int_val),
+            HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
+
+  // set int32
+  EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameInt, kIntVal));
+
+  // check that the value exists
+  EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameInt));
+
+  // get an in-existent value from an existent key
+  EXPECT_EQ(RegKey::GetValue(kFullRkey1, L"bogus", &int_val),
+            HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
+
+  // read it back
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameInt, &int_val));
+  EXPECT_EQ(int_val, kIntVal);
+
+  // delete the value
+  EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameInt));
+
+  // check that the value is gone
+  EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameInt));
+
+  // set int64
+  EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameInt64, kIntVal64));
+
+  // check that the value exists
+  EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameInt64));
+
+  // read it back
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameInt64, &int64_val));
+  EXPECT_EQ(int64_val, kIntVal64);
+
+  // delete the value
+  EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameInt64));
+
+  // check that the value is gone
+  EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameInt64));
+
+  // set float
+  EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameFloat, kFloatVal));
+
+  // check that the value exists
+  EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameFloat));
+
+  // read it back
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameFloat, &float_val));
+  EXPECT_EQ(float_val, kFloatVal);
+
+  // delete the value
+  EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameFloat));
+
+  // check that the value is gone
+  EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameFloat));
+  EXPECT_FAILED(RegKey::GetValue(kFullRkey1, kValNameFloat, &float_val));
+
+  // set double
+  EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameDouble, kDoubleVal));
+
+  // check that the value exists
+  EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameDouble));
+
+  // read it back
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameDouble, &double_val));
+  EXPECT_EQ(double_val, kDoubleVal);
+
+  // delete the value
+  EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameDouble));
+
+  // check that the value is gone
+  EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameDouble));
+  EXPECT_FAILED(RegKey::GetValue(kFullRkey1, kValNameDouble, &double_val));
+
+  // set string
+  EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameStr, kStrVal));
+
+  // check that the value exists
+  EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameStr));
+
+  // read it back
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameStr, &str_val));
+  EXPECT_TRUE(lstrcmp(str_val, kStrVal) == 0);
+  delete[] str_val;
+
+  // read it back in std::wstring
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameStr, &wstr_val));
+  EXPECT_STREQ(wstr_val.c_str(), kStrVal);
+
+  // get an in-existent value from an existent key
+  EXPECT_EQ(RegKey::GetValue(kFullRkey1, L"bogus", &str_val),
+            HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
+
+  // delete the value
+  EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameStr));
+
+  // check that the value is gone
+  EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameStr));
+
+  // set binary
+  EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary,
+      reinterpret_cast<const uint8*>(kBinaryVal), sizeof(kBinaryVal)-1));
+
+  // check that the value exists
+  EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+  // read it back
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary,
+      &binary_val, &uint8_count));
+  EXPECT_TRUE(memcmp(binary_val, kBinaryVal, sizeof(kBinaryVal)-1) == 0);
+  delete[] binary_val;
+
+  // delete the value
+  EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary));
+
+  // check that the value is gone
+  EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+  // special case - set a binary value with length 0
+  EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary,
+      reinterpret_cast<const uint8*>(kBinaryVal), 0));
+
+  // check that the value exists
+  EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+  // read it back
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary,
+      &binary_val, &uint8_count));
+  EXPECT_EQ(uint8_count, 0);
+  EXPECT_TRUE(binary_val == NULL);
+  delete[] binary_val;
+
+  // delete the value
+  EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary));
+
+  // check that the value is gone
+  EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+  // special case - set a NULL binary value
+  EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary, NULL, 100));
+
+  // check that the value exists
+  EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+  // read it back
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary,
+                                    &binary_val, &uint8_count));
+  EXPECT_EQ(uint8_count, 0);
+  EXPECT_TRUE(binary_val == NULL);
+  delete[] binary_val;
+
+  // delete the value
+  EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary));
+
+  // check that the value is gone
+  EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+  // test read/write REG_MULTI_SZ value
+  std::vector<std::wstring> result;
+  EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr,
+      reinterpret_cast<const uint8*>(kMultiSZ), sizeof(kMultiSZ)));
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result));
+  EXPECT_EQ(result.size(), 3);
+  EXPECT_STREQ(result[0].c_str(), L"abc");
+  EXPECT_STREQ(result[1].c_str(), L"def");
+  EXPECT_STREQ(result[2].c_str(), L"P12345");
+  EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr,
+      reinterpret_cast<const uint8*>(kEmptyMultiSZ), sizeof(kEmptyMultiSZ)));
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result));
+  EXPECT_EQ(result.size(), 0);
+  // writing REG_MULTI_SZ value will automatically add ending null characters
+  EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr,
+      reinterpret_cast<const uint8*>(kInvalidMultiSZ), sizeof(kInvalidMultiSZ)));
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result));
+  EXPECT_EQ(result.size(), 1);
+  EXPECT_STREQ(result[0].c_str(), L"678");
+
+  // Run the following test only in dev machine
+  // This is because the build machine might not have admin privilege
+#ifdef IS_PRIVATE_BUILD
+  // get a temp file name
+  wchar_t temp_path[MAX_PATH] = {0};
+  EXPECT_LT(::GetTempPath(ARRAY_SIZE(temp_path), temp_path),
+            static_cast<DWORD>(ARRAY_SIZE(temp_path)));
+  wchar_t temp_file[MAX_PATH] = {0};
+  EXPECT_NE(::GetTempFileName(temp_path, L"rkut_",
+                              ::GetTickCount(), temp_file), 0);
+
+  // test save
+  EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1Subkey, kValNameInt, kIntVal));
+  EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1Subkey, kValNameInt64, kIntVal64));
+  EXPECT_SUCCEEDED(RegKey::Save(kFullRkey1Subkey, temp_file));
+  EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1Subkey, kValNameInt));
+  EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1Subkey, kValNameInt64));
+
+  // test restore
+  EXPECT_SUCCEEDED(RegKey::Restore(kFullRkey1Subkey, temp_file));
+  int_val = 0;
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1Subkey, kValNameInt, &int_val));
+  EXPECT_EQ(int_val, kIntVal);
+  int64_val = 0;
+  EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1Subkey,
+                                    kValNameInt64,
+                                    &int64_val));
+  EXPECT_EQ(int64_val, kIntVal64);
+
+  // delete the temp file
+  EXPECT_EQ(TRUE, ::DeleteFile(temp_file));
+#endif
+
+  // whack the whole key
+  EXPECT_SUCCEEDED(RegKey::DeleteKey(kFullRkey1));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32socketserver.cc b/talk/base/win32socketserver.cc
index 0915aaf..90bb010 100644
--- a/talk/base/win32socketserver.cc
+++ b/talk/base/win32socketserver.cc
@@ -168,7 +168,7 @@
 
   virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
                          LRESULT& result);
-  virtual void OnFinalMessage(HWND hWnd);
+  virtual void OnNcDestroy();
 
  private:
   bool OnSocketNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& result);
@@ -226,8 +226,13 @@
   return true;
 }
 
-void Win32Socket::EventSink::OnFinalMessage(HWND hWnd) {
-  delete this;
+void Win32Socket::EventSink::OnNcDestroy() {
+  if (parent_) {
+    LOG(LS_ERROR) << "EventSink hwnd is being destroyed, but the event sink"
+                     " hasn't yet been disposed.";
+  } else {
+    delete this;
+  }
 }
 
 /////////////////////////////////////////////////////////////////////////////
@@ -741,7 +746,10 @@
       // was a message for the dialog that it handled internally.
       // Otherwise, dispatch as usual via Translate/DispatchMessage.
       b = GetMessage(&msg, NULL, 0, 0);
-      if (b) {
+      if (b == -1) {
+        LOG_GLE(LS_ERROR) << "GetMessage failed.";
+        return false;
+      } else if(b) {
         if (!hdlg_ || !IsDialogMessage(hdlg_, &msg)) {
           TranslateMessage(&msg);
           DispatchMessage(&msg);
diff --git a/talk/base/win32socketserver_unittest.cc b/talk/base/win32socketserver_unittest.cc
new file mode 100644
index 0000000..8531861
--- /dev/null
+++ b/talk/base/win32socketserver_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2009 Google Inc. All Rights Reserved.
+
+
+#include "talk/base/gunit.h"
+#include "talk/base/socket_unittest.h"
+#include "talk/base/thread.h"
+#include "talk/base/win32socketserver.h"
+
+namespace talk_base {
+
+// Test that Win32SocketServer::Wait works as expected.
+TEST(Win32SocketServerTest, TestWait) {
+  Win32SocketServer server(NULL);
+  uint32 start = Time();
+  server.Wait(1000, true);
+  EXPECT_GE(TimeSince(start), 1000);
+}
+
+// Test that Win32Socket::Pump does not touch general Windows messages.
+TEST(Win32SocketServerTest, TestPump) {
+  Win32SocketServer server(NULL);
+  SocketServerScope scope(&server);
+  EXPECT_EQ(TRUE, PostMessage(NULL, WM_USER, 999, 0));
+  server.Pump();
+  MSG msg;
+  EXPECT_EQ(TRUE, PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
+  EXPECT_EQ(WM_USER, msg.message);
+  EXPECT_EQ(999, msg.wParam);
+}
+
+// Test that Win32Socket passes all the generic Socket tests.
+class Win32SocketTest : public SocketTest {
+ protected:
+  Win32SocketTest() : server_(NULL), scope_(&server_) {}
+  Win32SocketServer server_;
+  SocketServerScope scope_;
+};
+
+TEST_F(Win32SocketTest, TestConnect) {
+  SocketTest::TestConnect();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithDnsLookup) {
+  SocketTest::TestConnectWithDnsLookup();
+}
+
+TEST_F(Win32SocketTest, TestConnectFail) {
+  SocketTest::TestConnectFail();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithDnsLookupFail) {
+  SocketTest::TestConnectWithDnsLookupFail();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithClosedSocket) {
+  SocketTest::TestConnectWithClosedSocket();
+}
+
+TEST_F(Win32SocketTest, TestServerCloseDuringConnect) {
+  SocketTest::TestServerCloseDuringConnect();
+}
+
+TEST_F(Win32SocketTest, TestClientCloseDuringConnect) {
+  SocketTest::TestClientCloseDuringConnect();
+}
+
+TEST_F(Win32SocketTest, TestServerClose) {
+  SocketTest::TestServerClose();
+}
+
+TEST_F(Win32SocketTest, TestCloseInClosedCallback) {
+  SocketTest::TestCloseInClosedCallback();
+}
+
+TEST_F(Win32SocketTest, TestSocketServerWait) {
+  SocketTest::TestSocketServerWait();
+}
+
+TEST_F(Win32SocketTest, TestTcp) {
+  SocketTest::TestTcp();
+}
+
+TEST_F(Win32SocketTest, TestUdp) {
+  SocketTest::TestUdp();
+}
+
+TEST_F(Win32SocketTest, TestGetSetOptions) {
+  SocketTest::TestGetSetOptions();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32toolhelp.h b/talk/base/win32toolhelp.h
new file mode 100644
index 0000000..64a191a
--- /dev/null
+++ b/talk/base/win32toolhelp.h
@@ -0,0 +1,166 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+
+#ifndef TALK_BASE_WIN32TOOLHELP_H_
+#define TALK_BASE_WIN32TOOLHELP_H_
+
+#ifndef WIN32
+#error WIN32 Only
+#endif
+
+#include "talk/base/win32.h"
+
+// Should be included first, but that causes redefinitions.
+#include <tlhelp32.h>
+
+#include "talk/base/constructormagic.h"
+
+namespace talk_base {
+
+// The toolhelp api used to enumerate processes and their modules
+// on Windows is very repetetive and clunky to use. This little
+// template wraps it to make it a little more programmer friendly.
+//
+// Traits: Traits type that adapts the enumerator to the corresponding
+//         win32 toolhelp api. Each traits class need to:
+//         - define the type of the enumerated data as a public symbol Type
+//
+//         - implement bool First(HANDLE, T*) normally calls a
+//           Xxxx32First method in the toolhelp API. Ex Process32First(...)
+//
+//         - implement bool Next(HANDLE, T*) normally calls a
+//           Xxxx32Next method in the toolhelp API. Ex Process32Next(...)
+//
+//         - implement bool CloseHandle(HANDLE)
+//
+template<typename Traits>
+class ToolhelpEnumeratorBase {
+ public:
+  ToolhelpEnumeratorBase(HANDLE snapshot)
+      : snapshot_(snapshot), broken_(false), first_(true) {
+
+    // Clear out the Traits::Type structure instance.
+    Zero(&current_);
+  }
+
+  virtual ~ToolhelpEnumeratorBase() {
+    Close();
+  }
+
+  // Moves forward to the next object using the First and Next
+  // pointers. If either First or Next ever indicates an failure
+  // all subsequent calls to this method will fail; the enumerator
+  // object is considered broken.
+  bool Next() {
+    if (!Valid()) {
+      return false;
+    }
+
+    // Move the iteration forward.
+    current_.dwSize = sizeof(typename Traits::Type);
+    bool incr_ok = false;
+    if (first_) {
+      incr_ok = Traits::First(snapshot_, &current_);
+      first_ = false;
+    } else {
+      incr_ok = Traits::Next(snapshot_, &current_);
+    }
+
+    if (!incr_ok) {
+      Zero(&current_);
+      broken_ = true;
+    }
+
+    return incr_ok;
+  }
+
+  const typename Traits::Type& current() const {
+    return current_;
+  }
+
+  void Close() {
+    if (snapshot_ != INVALID_HANDLE_VALUE) {
+      Traits::CloseHandle(snapshot_);
+      snapshot_ = INVALID_HANDLE_VALUE;
+    }
+  }
+
+ private:
+  // Checks the state of the snapshot handle.
+  bool Valid() {
+    return snapshot_ != INVALID_HANDLE_VALUE && !broken_;
+  }
+
+  static void Zero(typename Traits::Type* buff) {
+    ZeroMemory(buff, sizeof(typename Traits::Type));
+  }
+
+  HANDLE snapshot_;
+  typename Traits::Type current_;
+  bool broken_;
+  bool first_;
+};
+
+class ToolhelpTraits {
+ public:
+  static HANDLE CreateSnapshot(uint32 flags, uint32 process_id) {
+    return CreateToolhelp32Snapshot(flags, process_id);
+  }
+
+  static bool CloseHandle(HANDLE handle) {
+    return ::CloseHandle(handle) == TRUE;
+  }
+};
+
+class ToolhelpProcessTraits : public ToolhelpTraits {
+ public:
+  typedef PROCESSENTRY32 Type;
+
+  static bool First(HANDLE handle, Type* t) {
+    return ::Process32First(handle, t) == TRUE;
+  }
+
+  static bool Next(HANDLE handle, Type* t) {
+    return ::Process32Next(handle, t) == TRUE;
+  }
+};
+
+class ProcessEnumerator : public ToolhelpEnumeratorBase<ToolhelpProcessTraits> {
+ public:
+  ProcessEnumerator()
+      : ToolhelpEnumeratorBase(
+           ToolhelpProcessTraits::CreateSnapshot(TH32CS_SNAPPROCESS, 0)) {
+  }
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(ProcessEnumerator);
+};
+
+class ToolhelpModuleTraits : public ToolhelpTraits {
+ public:
+  typedef MODULEENTRY32 Type;
+
+  static bool First(HANDLE handle, Type* t) {
+    return ::Module32First(handle, t) == TRUE;
+  }
+
+  static bool Next(HANDLE handle, Type* t) {
+    return ::Module32Next(handle, t) == TRUE;
+  }
+};
+
+class ModuleEnumerator : public ToolhelpEnumeratorBase<ToolhelpModuleTraits> {
+ public:
+  explicit ModuleEnumerator(uint32 process_id)
+      : ToolhelpEnumeratorBase(
+            ToolhelpModuleTraits::CreateSnapshot(TH32CS_SNAPMODULE,
+                                                 process_id)) {
+  }
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(ModuleEnumerator);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WIN32TOOLHELP_H_
diff --git a/talk/base/win32toolhelp_unittest.cc b/talk/base/win32toolhelp_unittest.cc
new file mode 100644
index 0000000..529bef9
--- /dev/null
+++ b/talk/base/win32toolhelp_unittest.cc
@@ -0,0 +1,296 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/win32toolhelp.h"
+
+namespace talk_base {
+
+typedef struct {
+  // Required to match the toolhelp api struct 'design'.
+  DWORD dwSize;
+  int a;
+  uint32 b;
+} TestData;
+
+class Win32ToolhelpTest : public testing::Test {
+ public:
+  Win32ToolhelpTest() {
+  }
+
+  HANDLE AsHandle() {
+    return reinterpret_cast<HANDLE>(this);
+  }
+
+  static Win32ToolhelpTest* AsFixture(HANDLE handle) {
+    return reinterpret_cast<Win32ToolhelpTest*>(handle);
+  }
+
+  static bool First(HANDLE handle, TestData* d) {
+    Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle);
+    // This method should be called only once for every test.
+    // If it is called more than once it return false which
+    // should break the test.
+    EXPECT_EQ(0, tst->first_called_); // Just to be safe.
+    if (tst->first_called_ > 0) {
+      return false;
+    }
+
+    *d = kTestData[0];
+    tst->index_ = 1;
+    ++(tst->first_called_);
+    return true;
+  }
+
+  static bool Next(HANDLE handle, TestData* d) {
+    Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle);
+    ++(tst->next_called_);
+
+    if (tst->index_ >= kTestDataSize) {
+      return FALSE;
+    }
+
+    *d = kTestData[tst->index_];
+    ++(tst->index_);
+    return true;
+  }
+
+  static bool Fail(HANDLE handle, TestData* d) {
+    Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle);
+    ++(tst->fail_called_);
+    return false;
+  }
+
+  static bool CloseHandle(HANDLE handle) {
+    Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle);
+    ++(tst->close_handle_called_);
+    return true;
+  }
+
+ protected:
+  virtual void SetUp() {
+    fail_called_ = 0;
+    first_called_ = 0;
+    next_called_ = 0;
+    close_handle_called_ = 0;
+    index_ = 0;
+  }
+
+  static bool AllZero(const TestData& data) {
+    return data.dwSize == 0 && data.a == 0 && data.b == 0;
+  }
+
+  static bool Equals(const TestData& expected, const TestData& actual) {
+    return expected.dwSize == actual.dwSize
+        && expected.a == actual.a
+        && expected.b == actual.b;
+  }
+
+  bool CheckCallCounters(int first, int next, int fail, int close) {
+    bool match = first_called_ == first && next_called_ == next
+      && fail_called_ == fail && close_handle_called_ == close;
+
+    if (!match) {
+      LOG(LS_ERROR) << "Expected: ("
+                    << first << ", "
+                    << next << ", "
+                    << fail << ", "
+                    << close << ")";
+
+      LOG(LS_ERROR) << "Actual: ("
+                    << first_called_ << ", "
+                    << next_called_ << ", "
+                    << fail_called_ << ", "
+                    << close_handle_called_ << ")";
+    }
+    return match;
+  }
+
+  static const int kTestDataSize = 3;
+  static const TestData kTestData[];
+  int index_;
+  int first_called_;
+  int fail_called_;
+  int next_called_;
+  int close_handle_called_;
+};
+
+const TestData Win32ToolhelpTest::kTestData[] = {
+  {1, 1, 1}, {2, 2, 2}, {3, 3, 3}
+};
+
+
+class TestTraits {
+ public:
+  typedef TestData Type;
+
+  static bool First(HANDLE handle, Type* t) {
+    return Win32ToolhelpTest::First(handle, t);
+  }
+
+  static bool Next(HANDLE handle, Type* t) {
+    return Win32ToolhelpTest::Next(handle, t);
+  }
+
+  static bool CloseHandle(HANDLE handle) {
+    return Win32ToolhelpTest::CloseHandle(handle);
+  }
+};
+
+class BadFirstTraits {
+ public:
+  typedef TestData Type;
+
+  static bool First(HANDLE handle, Type* t) {
+    return Win32ToolhelpTest::Fail(handle, t);
+  }
+
+  static bool Next(HANDLE handle, Type* t) {
+    // This should never be called.
+    ADD_FAILURE();
+    return false;
+  }
+
+  static bool CloseHandle(HANDLE handle) {
+    return Win32ToolhelpTest::CloseHandle(handle);
+  }
+};
+
+class BadNextTraits {
+ public:
+  typedef TestData Type;
+
+  static bool First(HANDLE handle, Type* t) {
+    return Win32ToolhelpTest::First(handle, t);
+  }
+
+  static bool Next(HANDLE handle, Type* t) {
+    return Win32ToolhelpTest::Fail(handle, t);
+  }
+
+  static bool CloseHandle(HANDLE handle) {
+    return Win32ToolhelpTest::CloseHandle(handle);
+  }
+};
+
+// The toolhelp in normally inherited but most of
+// these tests only excercise the methods from the
+// traits therefore I use a typedef to make the
+// test code easier to read.
+typedef talk_base::ToolhelpEnumeratorBase<TestTraits> EnumeratorForTest;
+
+TEST_F(Win32ToolhelpTest, TestNextWithInvalidCtorHandle) {
+  EnumeratorForTest t(INVALID_HANDLE_VALUE);
+
+  EXPECT_FALSE(t.Next());
+  EXPECT_TRUE(CheckCallCounters(0, 0, 0, 0));
+}
+
+// Tests that Next() returns false if the first-pointer
+// function fails.
+TEST_F(Win32ToolhelpTest, TestNextFirstFails) {
+  typedef talk_base::ToolhelpEnumeratorBase<BadFirstTraits> BadEnumerator;
+  talk_base::scoped_ptr<BadEnumerator> t(new BadEnumerator(AsHandle()));
+
+  // If next ever fails it shall always fail.
+  EXPECT_FALSE(t->Next());
+  EXPECT_FALSE(t->Next());
+  EXPECT_FALSE(t->Next());
+  t.reset();
+  EXPECT_TRUE(CheckCallCounters(0, 0, 1, 1));
+}
+
+// Tests that Next() returns false if the next-pointer
+// function fails.
+TEST_F(Win32ToolhelpTest, TestNextNextFails) {
+  typedef talk_base::ToolhelpEnumeratorBase<BadNextTraits> BadEnumerator;
+  talk_base::scoped_ptr<BadEnumerator> t(new BadEnumerator(AsHandle()));
+
+  // If next ever fails it shall always fail. No more calls
+  // shall be dispatched to Next(...).
+  EXPECT_TRUE(t->Next());
+  EXPECT_FALSE(t->Next());
+  EXPECT_FALSE(t->Next());
+  t.reset();
+  EXPECT_TRUE(CheckCallCounters(1, 0, 1, 1));
+}
+
+
+// Tests that current returns an object is all zero's
+// if Next() hasn't been called.
+TEST_F(Win32ToolhelpTest, TestCurrentNextNotCalled) {
+  talk_base::scoped_ptr<EnumeratorForTest> t(new EnumeratorForTest(AsHandle()));
+  EXPECT_TRUE(AllZero(t->current()));
+  t.reset();
+  EXPECT_TRUE(CheckCallCounters(0, 0, 0, 1));
+}
+
+// Tests the simple everything works path through the code.
+TEST_F(Win32ToolhelpTest, TestCurrentNextCalled) {
+  talk_base::scoped_ptr<EnumeratorForTest> t(new EnumeratorForTest(AsHandle()));
+
+  EXPECT_TRUE(t->Next());
+  EXPECT_TRUE(Equals(t->current(), kTestData[0]));
+  EXPECT_TRUE(t->Next());
+  EXPECT_TRUE(Equals(t->current(), kTestData[1]));
+  EXPECT_TRUE(t->Next());
+  EXPECT_TRUE(Equals(t->current(), kTestData[2]));
+  EXPECT_FALSE(t->Next());
+  t.reset();
+  EXPECT_TRUE(CheckCallCounters(1, 3, 0, 1));
+}
+
+TEST_F(Win32ToolhelpTest, TestCurrentProcess) {
+  int size = MAX_PATH;
+  WCHAR buf[MAX_PATH];
+  GetModuleFileName(NULL, buf, ARRAY_SIZE(buf));
+  std::wstring name = ToUtf16(Pathname(ToUtf8(buf)).filename());
+
+  talk_base::ProcessEnumerator processes;
+  bool found = false;
+  while (processes.Next()) {
+    if (!name.compare(processes.current().szExeFile)) {
+      found = true;
+      break;
+    }
+  }
+  EXPECT_TRUE(found);
+
+  talk_base::ModuleEnumerator modules(processes.current().th32ProcessID);
+  found = false;
+  while (modules.Next()) {
+    if (!name.compare(modules.current().szModule)) {
+      found = true;
+      break;
+    }
+  }
+  EXPECT_TRUE(found);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32window.cc b/talk/base/win32window.cc
index 0e7761f..5b6275e 100644
--- a/talk/base/win32window.cc
+++ b/talk/base/win32window.cc
@@ -103,12 +103,12 @@
 LRESULT Win32Window::WndProc(HWND hwnd, UINT uMsg,
                              WPARAM wParam, LPARAM lParam) {
   Win32Window* that = reinterpret_cast<Win32Window*>(
-      ::GetWindowLongPtr(hwnd, GWL_USERDATA));
+      ::GetWindowLongPtr(hwnd, GWLP_USERDATA));
   if (!that && (WM_CREATE == uMsg)) {
     CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
     that = static_cast<Win32Window*>(cs->lpCreateParams);
     that->wnd_ = hwnd;
-    ::SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(that));
+    ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(that));
   }
   if (that) {
     LRESULT result;
@@ -120,9 +120,9 @@
       }
     }
     if (WM_NCDESTROY == uMsg) {
-      ::SetWindowLongPtr(hwnd, GWL_USERDATA, NULL);
+      ::SetWindowLongPtr(hwnd, GWLP_USERDATA, NULL);
       that->wnd_ = NULL;
-      that->OnDestroyed();
+      that->OnNcDestroy();
     }
     if (handled) {
       return result;
diff --git a/talk/base/win32window.h b/talk/base/win32window.h
index 66a56ce..992c8e7 100644
--- a/talk/base/win32window.h
+++ b/talk/base/win32window.h
@@ -2,26 +2,26 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
@@ -59,7 +59,7 @@
                          LRESULT& result);
 
   virtual bool OnClose() { return true; }
-  virtual void OnDestroyed() { }
+  virtual void OnNcDestroy() { }
 
  private:
   static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
diff --git a/talk/base/win32window_unittest.cc b/talk/base/win32window_unittest.cc
new file mode 100644
index 0000000..96173b7
--- /dev/null
+++ b/talk/base/win32window_unittest.cc
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/common.h"
+#include "talk/base/win32window.h"
+#include "talk/base/logging.h"
+
+static LRESULT kDummyResult = 0x1234ABCD;
+
+class TestWindow : public talk_base::Win32Window {
+ public:
+  TestWindow() : destroyed_(false) { memset(&msg_, 0, sizeof(msg_)); }
+  const MSG& msg() const { return msg_; }
+  bool destroyed() const { return destroyed_; }
+
+  virtual bool OnMessage(UINT uMsg, WPARAM wParam,
+                         LPARAM lParam, LRESULT& result) {
+    msg_.message = uMsg;
+    msg_.wParam = wParam;
+    msg_.lParam = lParam;
+    result = kDummyResult;
+    return true;
+  }
+  virtual void OnNcDestroy() {
+    destroyed_ = true;
+  }
+
+ private:
+  MSG msg_;
+  bool destroyed_;
+};
+
+TEST(Win32WindowTest, Basics) {
+  TestWindow wnd;
+  EXPECT_TRUE(wnd.handle() == NULL);
+  EXPECT_FALSE(wnd.destroyed());
+  EXPECT_TRUE(wnd.Create(0, L"Test", 0, 0, 0, 0, 100, 100));
+  EXPECT_TRUE(wnd.handle() != NULL);
+  EXPECT_EQ(kDummyResult, ::SendMessage(wnd.handle(), WM_USER, 1, 2));
+  EXPECT_EQ(WM_USER, wnd.msg().message);
+  EXPECT_EQ(1, wnd.msg().wParam);
+  EXPECT_EQ(2, wnd.msg().lParam);
+  wnd.Destroy();
+  EXPECT_TRUE(wnd.handle() == NULL);
+  EXPECT_TRUE(wnd.destroyed());
+}
+
+TEST(Win32WindowTest, MultipleWindows) {
+  TestWindow wnd1, wnd2;
+  EXPECT_TRUE(wnd1.Create(0, L"Test", 0, 0, 0, 0, 100, 100));
+  EXPECT_TRUE(wnd2.Create(0, L"Test", 0, 0, 0, 0, 100, 100));
+  EXPECT_TRUE(wnd1.handle() != NULL);
+  EXPECT_TRUE(wnd2.handle() != NULL);
+  wnd1.Destroy();
+  wnd2.Destroy();
+  EXPECT_TRUE(wnd2.handle() == NULL);
+  EXPECT_TRUE(wnd1.handle() == NULL);
+}
diff --git a/talk/base/win32windowpicker.cc b/talk/base/win32windowpicker.cc
new file mode 100644
index 0000000..b674d35
--- /dev/null
+++ b/talk/base/win32windowpicker.cc
@@ -0,0 +1,115 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#include "talk/base/win32windowpicker.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+namespace {
+
+// Window class names that we want to filter out.
+const char kProgramManagerClass[] = "Progman";
+const char kButtonClass[] = "Button";
+
+}  // namespace
+
+BOOL CALLBACK Win32WindowPicker::EnumProc(HWND hwnd, LPARAM l_param) {
+  WindowDescriptionList* descriptions =
+      reinterpret_cast<WindowDescriptionList*>(l_param);
+
+  // Skip windows that are invisible, minimized, have no title, or are owned,
+  // unless they have the app window style set. Except for minimized windows,
+  // this is what Alt-Tab does.
+  // TODO: Figure out how to grab a thumbnail of a minimized window and
+  // include them in the list.
+  int len = GetWindowTextLength(hwnd);
+  HWND owner = GetWindow(hwnd, GW_OWNER);
+  LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
+  if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) ||
+      (owner && !(exstyle & WS_EX_APPWINDOW))) {
+    // TODO: Investigate if windows without title still could be
+    // interesting to share. We could use the name of the process as title:
+    //
+    // GetWindowThreadProcessId()
+    // OpenProcess()
+    // QueryFullProcessImageName()
+    return TRUE;
+  }
+
+  // Skip the Program Manager window and the Start button.
+  TCHAR class_name_w[500];
+  ::GetClassName(hwnd, class_name_w, 500);
+  std::string class_name = ToUtf8(class_name_w, wcslen(class_name_w));
+  if (class_name == kProgramManagerClass || class_name == kButtonClass) {
+    // We don't want the Program Manager window nor the Start button.
+    return TRUE;
+  }
+
+  TCHAR window_title[500];
+  GetWindowText(hwnd, window_title, ARRAY_SIZE(window_title));
+  std::string title = ToUtf8(window_title, wcslen(window_title));
+  WindowId id(hwnd);
+  WindowDescription desc(id, title);
+  descriptions->push_back(desc);
+  return TRUE;
+}
+
+BOOL CALLBACK Win32WindowPicker::MonitorEnumProc(HMONITOR h_monitor,
+                                                 HDC hdc_monitor,
+                                                 LPRECT lprc_monitor,
+                                                 LPARAM l_param) {
+  DesktopDescriptionList* desktop_desc =
+      reinterpret_cast<DesktopDescriptionList*>(l_param);
+
+  DesktopId id(h_monitor, desktop_desc->size());
+  // TODO: Figure out an appropriate desktop title.
+  DesktopDescription desc(id, "");
+  desktop_desc->push_back(desc);
+  return TRUE;
+}
+
+Win32WindowPicker::Win32WindowPicker() {
+}
+
+bool Win32WindowPicker::Init() {
+  return true;
+}
+// TODO: Consider changing enumeration to clear() descriptions
+// before append().
+bool Win32WindowPicker::GetWindowList(WindowDescriptionList* descriptions) {
+  LPARAM desc = reinterpret_cast<LPARAM>(descriptions);
+  return EnumWindows(Win32WindowPicker::EnumProc, desc) != FALSE;
+}
+
+bool Win32WindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) {
+  // Create a fresh WindowDescriptionList so that we can use desktop_desc.size()
+  // in MonitorEnumProc to compute the desktop index.
+  DesktopDescriptionList desktop_desc;
+  HDC hdc = GetDC(NULL);
+  bool success = false;
+  if (EnumDisplayMonitors(hdc, NULL, Win32WindowPicker::MonitorEnumProc,
+      reinterpret_cast<LPARAM>(&desktop_desc)) != FALSE) {
+    // Append the desktop descriptions to the end of the returned descriptions.
+    descriptions->insert(descriptions->end(), desktop_desc.begin(),
+                         desktop_desc.end());
+    success = true;
+  }
+  ReleaseDC(NULL, hdc);
+  return success;
+}
+
+bool Win32WindowPicker::IsVisible(const WindowId& id) {
+  return (::IsWindow(id.id()) != FALSE && ::IsWindowVisible(id.id()) != FALSE);
+}
+
+bool Win32WindowPicker::MoveToFront(const WindowId& id) {
+  return SetForegroundWindow(id.id()) != FALSE;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32windowpicker.h b/talk/base/win32windowpicker.h
new file mode 100644
index 0000000..d5f779b
--- /dev/null
+++ b/talk/base/win32windowpicker.h
@@ -0,0 +1,31 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#ifndef TALK_BASE_WIN32WINDOWPICKER_H_
+#define TALK_BASE_WIN32WINDOWPICKER_H_
+
+#include "talk/base/win32.h"
+#include "talk/base/windowpicker.h"
+
+namespace talk_base {
+
+class Win32WindowPicker : public WindowPicker {
+ public:
+  Win32WindowPicker();
+  virtual bool Init();
+  virtual bool IsVisible(const WindowId& id);
+  virtual bool MoveToFront(const WindowId& id);
+  virtual bool GetWindowList(WindowDescriptionList* descriptions);
+  virtual bool GetDesktopList(DesktopDescriptionList* descriptions);
+
+ protected:
+  static BOOL CALLBACK EnumProc(HWND hwnd, LPARAM l_param);
+  static BOOL CALLBACK MonitorEnumProc(HMONITOR h_monitor,
+                                       HDC hdc_monitor,
+                                       LPRECT lprc_monitor,
+                                       LPARAM l_param);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WIN32WINDOWPICKER_H_
diff --git a/talk/base/win32windowpicker_unittest.cc b/talk/base/win32windowpicker_unittest.cc
new file mode 100644
index 0000000..065193e
--- /dev/null
+++ b/talk/base/win32windowpicker_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#include "talk/base/gunit.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/win32window.h"
+#include "talk/base/win32windowpicker.h"
+#include "talk/base/windowpicker.h"
+
+#ifndef WIN32
+#error Only for Windows
+#endif
+
+namespace talk_base {
+
+static const TCHAR* kVisibleWindowTitle = L"Visible Window";
+static const TCHAR* kInvisibleWindowTitle = L"Invisible Window";
+
+class Win32WindowPickerForTest : public Win32WindowPicker {
+ public:
+  Win32WindowPickerForTest() {
+    EXPECT_TRUE(visible_window_.Create(NULL, kVisibleWindowTitle, WS_VISIBLE,
+                                       0, 0, 0, 0, 0));
+    EXPECT_TRUE(invisible_window_.Create(NULL, kInvisibleWindowTitle, 0,
+                                         0, 0, 0, 0, 0));
+  }
+
+  ~Win32WindowPickerForTest() {
+    visible_window_.Destroy();
+    invisible_window_.Destroy();
+  }
+
+  virtual bool GetWindowList(WindowDescriptionList* descriptions) {
+    if (!Win32WindowPicker::EnumProc(visible_window_.handle(),
+                                     reinterpret_cast<LPARAM>(descriptions))) {
+      return false;
+    }
+    if (!Win32WindowPicker::EnumProc(invisible_window_.handle(),
+                                     reinterpret_cast<LPARAM>(descriptions))) {
+      return false;
+    }
+    return true;
+  }
+
+  Win32Window* visible_window() {
+    return &visible_window_;
+  }
+
+  Win32Window* invisible_window() {
+    return &invisible_window_;
+  }
+
+ private:
+  Win32Window visible_window_;
+  Win32Window invisible_window_;
+};
+
+TEST(Win32WindowPickerTest, TestGetWindowList) {
+  Win32WindowPickerForTest window_picker;
+  WindowDescriptionList descriptions;
+  EXPECT_TRUE(window_picker.GetWindowList(&descriptions));
+  EXPECT_EQ(1, descriptions.size());
+  WindowDescription desc = descriptions.front();
+  EXPECT_EQ(window_picker.visible_window()->handle(), desc.id().id());
+  TCHAR window_title[500];
+  GetWindowText(window_picker.visible_window()->handle(), window_title,
+                ARRAY_SIZE(window_title));
+  EXPECT_EQ(ToUtf8(window_title, wcslen(window_title)),
+            ToUtf8(kVisibleWindowTitle, wcslen(kVisibleWindowTitle)));
+}
+
+TEST(Win32WindowPickerTest, TestIsVisible) {
+  Win32WindowPickerForTest window_picker;
+  HWND visible_id = window_picker.visible_window()->handle();
+  HWND invisible_id = window_picker.invisible_window()->handle();
+  EXPECT_TRUE(window_picker.IsVisible(WindowId(visible_id)));
+  EXPECT_FALSE(window_picker.IsVisible(WindowId(invisible_id)));
+}
+
+TEST(Win32WindowPickerTest, TestMoveToFront) {
+  Win32WindowPickerForTest window_picker;
+  HWND visible_id = window_picker.visible_window()->handle();
+  HWND invisible_id = window_picker.invisible_window()->handle();
+
+  // There are a number of condition where SetForegroundWindow might
+  // fail depending on the state of the calling process. To be on the
+  // safe side we doesn't expect MoveToFront to return true, just test
+  // that we don't crash.
+  window_picker.MoveToFront(WindowId(visible_id));
+  window_picker.MoveToFront(WindowId(invisible_id));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/window.h b/talk/base/window.h
new file mode 100644
index 0000000..b855193
--- /dev/null
+++ b/talk/base/window.h
@@ -0,0 +1,127 @@
+/*
+ * 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_WINDOW_H_
+#define TALK_BASE_WINDOW_H_
+
+// Define platform specific window types.
+#if defined(LINUX)
+typedef unsigned long Window;  // Avoid include <X11/Xlib.h>.
+#elif defined(WIN32)
+// We commonly include win32.h in talk/base so just include it here.
+#include "talk/base/win32.h"  // Include HWND, HMONITOR.
+#elif defined(OSX)
+typedef unsigned int CGWindowID;
+typedef unsigned int CGDirectDisplayID;
+#endif
+
+namespace talk_base {
+
+class WindowId {
+ public:
+  // Define WindowT for each platform.
+#if defined(LINUX)
+  typedef Window WindowT;
+#elif defined(WIN32)
+  typedef HWND WindowT;
+#elif defined(OSX)
+  typedef CGWindowID WindowT;
+#else
+  typedef unsigned int WindowT;
+#endif
+
+  static WindowId Cast(int id) {
+#if defined(WIN32)
+    return WindowId(reinterpret_cast<WindowId::WindowT>(id));
+#else
+    return WindowId(static_cast<WindowId::WindowT>(id));
+#endif
+  }
+
+  WindowId() : id_(0) {}
+  WindowId(const WindowT& id) : id_(id) {}  // NOLINT
+  const WindowT& id() const { return id_; }
+  bool IsValid() const { return id_ != 0; }
+  bool Equals(const WindowId& other) const {
+    return id_ == other.id();
+  }
+
+ private:
+  WindowT id_;
+};
+
+class DesktopId {
+ public:
+  // Define DesktopT for each platform.
+#if defined(LINUX)
+  typedef Window DesktopT;
+#elif defined(WIN32)
+  typedef HMONITOR DesktopT;
+#elif defined(OSX)
+  typedef CGDirectDisplayID DesktopT;
+#else
+  typedef unsigned int DesktopT;
+#endif
+
+  static DesktopId Cast(int id, int index) {
+#if defined(WIN32)
+    return DesktopId(reinterpret_cast<DesktopId::DesktopT>(id), index);
+#else
+    return DesktopId(static_cast<DesktopId::DesktopT>(id), index);
+#endif
+  }
+
+  DesktopId() : id_(0), index_(-1) {}
+  DesktopId(const DesktopT& id, int index)  // NOLINT
+      : id_(id), index_(index) {
+  }
+  const DesktopT& id() const { return id_; }
+  int index() const { return index_; }
+  bool IsValid() const { return index_ != -1; }
+  bool Equals(const DesktopId& other) const {
+    return id_ == other.id() && index_ == other.index();
+  }
+
+ private:
+  // Id is the platform specific desktop identifier.
+  DesktopT id_;
+  // Index is the desktop index as enumerated by each platform.
+  // Desktop capturer typically takes the index instead of id.
+  int index_;
+};
+
+// Window event types.
+enum WindowEvent {
+  WE_RESIZE = 0,
+  WE_CLOSE = 1,
+  WE_MINIMIZE = 2,
+  WE_RESTORE = 3,
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WINDOW_H_
diff --git a/talk/base/windowpicker.h b/talk/base/windowpicker.h
new file mode 100644
index 0000000..242fabc
--- /dev/null
+++ b/talk/base/windowpicker.h
@@ -0,0 +1,70 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+//         thorcarpenter@google.com (Thor Carpenter)
+
+#ifndef TALK_BASE_WINDOWPICKER_H_
+#define TALK_BASE_WINDOWPICKER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/window.h"
+
+namespace talk_base {
+
+class WindowDescription {
+ public:
+  WindowDescription() : id_() {}
+  WindowDescription(const WindowId& id, const std::string& title)
+      : id_(id), title_(title) {
+  }
+  const WindowId& id() const { return id_; }
+  void set_id(const WindowId& id) { id_ = id; }
+  const std::string& title() const { return title_; }
+  void set_title(const std::string& title) { title_ = title; }
+
+ private:
+  WindowId id_;
+  std::string title_;
+};
+
+class DesktopDescription {
+ public:
+  DesktopDescription() : id_() {}
+  DesktopDescription(const DesktopId& id, const std::string& title)
+      : id_(id), title_(title) {
+  }
+  const DesktopId& id() const { return id_; }
+  void set_id(const DesktopId& id) { id_ = id; }
+  const std::string& title() const { return title_; }
+  void set_title(const std::string& title) { title_ = title; }
+
+ private:
+  DesktopId id_;
+  std::string title_;
+};
+
+typedef std::vector<WindowDescription> WindowDescriptionList;
+typedef std::vector<DesktopDescription> DesktopDescriptionList;
+
+class WindowPicker {
+ public:
+  virtual ~WindowPicker() {}
+  virtual bool Init() = 0;
+
+  // TODO: Move this two methods to window.h when we no longer need to load
+  // CoreGraphics dynamically.
+  virtual bool IsVisible(const WindowId& id) = 0;
+  virtual bool MoveToFront(const WindowId& id) = 0;
+
+  // Gets a list of window description and appends to descriptions.
+  // Returns true if successful.
+  virtual bool GetWindowList(WindowDescriptionList* descriptions) = 0;
+  // Gets a list of desktop descriptions and appends to descriptions.
+  // Returns true if successful.
+  virtual bool GetDesktopList(DesktopDescriptionList* descriptions) = 0;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WINDOWPICKER_H_
diff --git a/talk/base/windowpickerfactory.h b/talk/base/windowpickerfactory.h
new file mode 100644
index 0000000..e001a2c
--- /dev/null
+++ b/talk/base/windowpickerfactory.h
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_WINDOWPICKERFACTORY_H_
+#define TALK_BASE_WINDOWPICKERFACTORY_H_
+
+#if defined(WIN32)
+#include "talk/base/win32windowpicker.h"
+#elif defined(OSX)
+#include "talk/base/macutils.h"
+#include "talk/base/macwindowpicker.h"
+#elif defined(LINUX)
+#include "talk/base/linuxwindowpicker.h"
+#endif
+
+#include "talk/base/windowpicker.h"
+
+namespace talk_base {
+
+class WindowPickerFactory {
+ public:
+  static WindowPicker* CreateWindowPicker() {
+#if defined(WIN32)
+    return new Win32WindowPicker();
+#elif defined(OSX)
+    return new MacWindowPicker();
+#elif defined(LINUX)
+    return new LinuxWindowPicker();
+#else
+    return NULL;
+#endif
+  }
+
+  static bool IsSupported() {
+#ifdef OSX
+    return GetOSVersionName() >= kMacOSLeopard;
+#else
+    return true;
+#endif
+  }
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WINDOWPICKERFACTORY_H_
diff --git a/talk/base/winfirewall_unittest.cc b/talk/base/winfirewall_unittest.cc
new file mode 100644
index 0000000..9987716
--- /dev/null
+++ b/talk/base/winfirewall_unittest.cc
@@ -0,0 +1,57 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/winfirewall.h"
+
+#include <objbase.h>
+
+namespace talk_base {
+
+TEST(WinFirewallTest, ReadStatus) {
+  ::CoInitialize(NULL);
+  WinFirewall fw;
+  HRESULT hr;
+  bool authorized;
+
+  EXPECT_FALSE(fw.QueryAuthorized("bogus.exe", &authorized));
+  EXPECT_TRUE(fw.Initialize(&hr));
+  EXPECT_EQ(S_OK, hr);
+
+  EXPECT_TRUE(fw.QueryAuthorized("bogus.exe", &authorized));
+
+  // Unless we mock out INetFwMgr we can't really have an expectation either way
+  // about whether we're authorized.  It will depend on the settings of the
+  // machine running the test.  Same goes for AddApplication.
+
+  fw.Shutdown();
+  EXPECT_FALSE(fw.QueryAuthorized("bogus.exe", &authorized));
+
+  ::CoUninitialize();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/winping.cc b/talk/base/winping.cc
index 1802e03..a16ac78 100644
--- a/talk/base/winping.cc
+++ b/talk/base/winping.cc
@@ -180,7 +180,7 @@
   }
 
   delete[] data_;
-  delete reply_;
+  delete[] reply_;
 }
 
 WinPing::PingResult WinPing::Ping(
diff --git a/talk/examples/call/call_main.cc b/talk/examples/call/call_main.cc
index b9f5696..c2be1e8 100644
--- a/talk/examples/call/call_main.cc
+++ b/talk/examples/call/call_main.cc
@@ -25,14 +25,15 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <cstdio>
+#include <cstring>
 #include <time.h>
 #include <iomanip>
 #include <iostream>
-#include <cstdio>
-#include <cstring>
 #include <vector>
-#include "talk/base/logging.h"
+
 #include "talk/base/flags.h"
+#include "talk/base/logging.h"
 #ifdef OSX
 #include "talk/base/macsocketserver.h"
 #endif
@@ -40,16 +41,19 @@
 #include "talk/base/stream.h"
 #include "talk/base/ssladapter.h"
 #include "talk/base/win32socketserver.h"
-#include "talk/p2p/base/constants.h"
-#include "talk/xmpp/xmppclientsettings.h"
 #include "talk/examples/login/xmppthread.h"
 #include "talk/examples/login/xmppauth.h"
 #include "talk/examples/login/xmpppump.h"
 #include "talk/examples/call/callclient.h"
 #include "talk/examples/call/console.h"
-#include "talk/session/phone/filemediaengine.h"
+#include "talk/examples/call/mediaenginefactory.h"
+#include "talk/p2p/base/constants.h"
+#ifdef ANDROID
+#include "talk/session/phone/androidmediaengine.h"
+#endif
 #include "talk/session/phone/mediasessionclient.h"
 #include "talk/session/phone/srtpfilter.h"
+#include "talk/xmpp/xmppclientsettings.h"
 
 class DebugLog : public sigslot::has_slots<> {
  public:
@@ -177,48 +181,18 @@
 static DebugLog debug_log_;
 static const int DEFAULT_PORT = 5222;
 
+#ifdef ANDROID
+static std::vector<cricket::AudioCodec> codecs;
+static const cricket::AudioCodec ISAC(103, "ISAC", 40000, 16000, 1, 0);
 
-cricket::MediaEngineInterface* CreateFileMediaEngine(const char* voice_in,
-                                                     const char* voice_out,
-                                                     const char* video_in,
-                                                     const char* video_out) {
-  cricket::FileMediaEngine* file_media_engine = new cricket::FileMediaEngine;
-  // Set the RTP dump file names.
-  if (voice_in) {
-    file_media_engine->set_voice_input_filename(voice_in);
-  }
-  if (voice_out) {
-    file_media_engine->set_voice_output_filename(voice_out);
-  }
-  if (video_in) {
-    file_media_engine->set_video_input_filename(video_in);
-  }
-  if (video_out) {
-    file_media_engine->set_video_output_filename(video_out);
-  }
+cricket::MediaEngine *AndroidMediaEngineFactory() {
+    cricket::FakeMediaEngine *engine = new cricket::FakeMediaEngine();
 
-  // Set voice and video codecs. TODO: The codecs actually depend on
-  // the the input voice and video streams.
-  std::vector<cricket::AudioCodec> voice_codecs;
-  voice_codecs.push_back(
-      cricket::AudioCodec(9, "G722", 16000, 0, 1, 0));
-  voice_codecs.push_back(
-      cricket::AudioCodec(0, "PCMU", 8000, 0, 1, 0));
-  voice_codecs.push_back(
-      cricket::AudioCodec(13, "CN", 8000, 0, 1, 0));
-  voice_codecs.push_back(
-      cricket::AudioCodec(105, "CN", 16000, 0, 1, 0));    
-  file_media_engine->set_voice_codecs(voice_codecs);
-  std::vector<cricket::VideoCodec> video_codecs;
-  video_codecs.push_back(
-      cricket::VideoCodec(97, "H264", 320, 240, 30, 0));
-  video_codecs.push_back(
-      cricket::VideoCodec(99, "H264-SVC", 640, 360, 30, 0));
-  file_media_engine->set_video_codecs(video_codecs);
-
-  return file_media_engine;
+    codecs.push_back(ISAC);
+    engine->SetAudioCodecs(codecs);
+    return engine;
 }
-
+#endif
 
 // TODO: Move this into Console.
 void Print(const char* chars) {
@@ -236,22 +210,25 @@
   // define options
   DEFINE_bool(a, false, "Turn on auto accept.");
   DEFINE_bool(d, false, "Turn on debugging.");
-  DEFINE_string(
-      protocol, "hybrid",
+  DEFINE_string(protocol, "hybrid",
       "Initial signaling protocol to use: jingle, gingle, or hybrid.");
-  DEFINE_string(
-      secure, "enable",
+  DEFINE_string(secure, "enable",
       "Disable or enable encryption: disable, enable, require.");
+  DEFINE_string(tls, "enable",
+      "Disable or enable tls: disable, enable, require.");
+  DEFINE_bool(allowplain, false, "Allow plain authentication");
   DEFINE_bool(testserver, false, "Use test server");
-  DEFINE_bool(plainserver, false, "Turn off tls and allow plain password.");
   DEFINE_int(portallocator, 0, "Filter out unwanted connection types.");
   DEFINE_string(filterhost, NULL, "Filter out the host from all candidates.");
   DEFINE_string(pmuc, "groupchat.google.com", "The persistant muc domain.");
   DEFINE_string(s, "talk.google.com", "The connection server to use.");
+  DEFINE_string(capsnode, "http://code.google.com/p/libjingle/call",
+                "Caps node: A URI identifying the app.");
+  DEFINE_string(capsver, "0.6",
+                "Caps ver: A string identifying the version of the app.");
   DEFINE_string(voiceinput, NULL, "RTP dump file for voice input.");
   DEFINE_string(voiceoutput, NULL, "RTP dump file for voice output.");
   DEFINE_string(videoinput, NULL, "RTP dump file for video input.");
-  DEFINE_string(yuvvideoinput, NULL, "YUV file for video input.");
   DEFINE_string(videooutput, NULL, "RTP dump file for video output.");
   DEFINE_bool(render, true, "Renders the video.");
   DEFINE_bool(debugsrtp, false, "Enable debugging for srtp.");
@@ -268,11 +245,14 @@
   bool debug = FLAG_d;
   std::string protocol = FLAG_protocol;
   bool test_server = FLAG_testserver;
-  bool plain_server = FLAG_plainserver;
+  bool allow_plain = FLAG_allowplain;
+  std::string tls = FLAG_tls;
   int32 portallocator_flags = FLAG_portallocator;
   std::string pmuc_domain = FLAG_pmuc;
   std::string server = FLAG_s;
   std::string secure = FLAG_secure;
+  std::string caps_node = FLAG_capsnode;
+  std::string caps_ver = FLAG_capsver;
   bool debugsrtp = FLAG_debugsrtp;
   bool render = FLAG_render;
 
@@ -342,15 +322,24 @@
   xcs.set_user(jid.node());
   xcs.set_resource("call");
   xcs.set_host(jid.domain());
-  xcs.set_use_tls(!test_server);
+  xcs.set_allow_plain(allow_plain);
 
-  if (plain_server) {
-    xcs.set_use_tls(false);
-    xcs.set_allow_plain(true);
+  if(tls == "disable") {
+    xcs.set_use_tls(buzz::TLS_DISABLED);
+  } else if (tls == "enable") {
+    xcs.set_use_tls(buzz::TLS_ENABLED);
+  } else if (tls == "require") {
+    xcs.set_use_tls(buzz::TLS_REQUIRED);
+  } else {
+    Print("Invalid TLS option, must be enable, disable, or require.\n");
+    return 1;
   }
+
   if (test_server) {
     pass.password() = jid.node();
     xcs.set_allow_plain(true);
+    xcs.set_use_tls(buzz::TLS_DISABLED);
+    xcs.set_test_server_domain("google.com");
   }
   xcs.set_pass(talk_base::CryptString(pass));
 
@@ -371,11 +360,14 @@
 
   talk_base::InitializeSSL();
 
+#ifdef ANDROID
+  InitAndroidMediaEngineFactory(AndroidMediaEngineFactory);
+#endif
 
 #if WIN32
   // Need to pump messages on our main thread on Windows.
   talk_base::Win32Thread w32_thread;
-  talk_base::ThreadManager::SetCurrent(&w32_thread);
+  talk_base::ThreadManager::Instance()->SetCurrentThread(&w32_thread);
 #endif
   talk_base::Thread* main_thread = talk_base::Thread::Current();
 #ifdef OSX
@@ -384,16 +376,18 @@
 #endif
 
   XmppPump pump;
-  CallClient *client = new CallClient(pump.client());
+  CallClient *client = new CallClient(pump.client(), caps_node, caps_ver);
 
   if (FLAG_voiceinput || FLAG_voiceoutput ||
       FLAG_videoinput || FLAG_videooutput) {
-    // If any dump file is specified, we use FileMediaEngine.
-    cricket::MediaEngineInterface* engine = CreateFileMediaEngine(
-        FLAG_voiceinput, FLAG_voiceoutput, FLAG_videoinput, FLAG_videooutput);
-    // The engine will be released by the client later.
+    // If any dump file is specified, we use a FileMediaEngine.
+    cricket::MediaEngineInterface* engine =
+        MediaEngineFactory::CreateFileMediaEngine(
+            FLAG_voiceinput, FLAG_voiceoutput,
+            FLAG_videoinput, FLAG_videooutput);
     client->SetMediaEngine(engine);
   }
+
   Console *console = new Console(main_thread, client);
   client->SetConsole(console);
   client->SetAutoAccept(auto_accept);
@@ -410,7 +404,7 @@
     pump.client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
   }
 
-  pump.DoLogin(xcs, new XmppSocket(true), NULL);
+  pump.DoLogin(xcs, new XmppSocket(buzz::TLS_REQUIRED), NULL);
   main_thread->Run();
   pump.DoDisconnect();
 
diff --git a/talk/examples/call/call_unittest.cc b/talk/examples/call/call_unittest.cc
new file mode 100644
index 0000000..d95f1dd
--- /dev/null
+++ b/talk/examples/call/call_unittest.cc
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Main function for all unit tests in talk/examples/call
+
+#include "talk/base/logging.h"
+#include "testing/base/public/gunit.h"
+
+int main(int argc, char **argv) {
+  talk_base::LogMessage::LogToDebug(talk_base::LogMessage::NO_LOGGING);
+  testing::ParseGUnitFlags(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
index 3c65272..89f964b 100644
--- a/talk/examples/call/callclient.cc
+++ b/talk/examples/call/callclient.cc
@@ -43,7 +43,6 @@
 #include "talk/examples/call/mucinvitesendtask.h"
 #include "talk/examples/call/friendinvitesendtask.h"
 #include "talk/examples/call/muc.h"
-#include "talk/examples/call/voicemailjidrequester.h"
 #include "talk/p2p/base/sessionmanager.h"
 #include "talk/p2p/client/basicportallocator.h"
 #include "talk/p2p/client/sessionmanagertask.h"
@@ -111,6 +110,7 @@
 "  record     Starts recording (just signalling; not actually recording.)\n"
 "  unrecord   Stops recording (just signalling; not actually recording.)\n"
 "  rmute [nick] Remote mute another participant.\n"
+"  block [nick] Block another participant.\n"
 "  quit       Quits the application.\n"
 "";
 
@@ -131,12 +131,12 @@
 "                      given JID and with optional bandwidth.\n"
 "  vcall [jid] [bw]    Initiates a video call to the user[/room] with\n"
 "                      the given JID and with optional bandwidth.\n"
-"  voicemail [jid]     Leave a voicemail for the user with the given JID.\n"
 "  join [room_jid]     Joins a multi-user-chat with room JID.\n"
 "  ljoin [room_name]   Joins a MUC by looking up JID from room name.\n"
 "  invite user [room]  Invites a friend to a multi-user-chat.\n"
 "  leave [room]        Leaves a multi-user-chat.\n"
 "  nick [nick]         Sets the nick.\n"
+"  priority [int]      Sets the priority.\n"
 "  getdevs             Prints the available media devices.\n"
 "  quit                Quits the application.\n"
 "";
@@ -171,6 +171,7 @@
     if (command == "accept") {
       cricket::CallOptions options;
       options.video_bandwidth = GetInt(words, 1, cricket::kAutoBandwidth);
+      options.has_video = true;
       Accept(options);
     } else if (command == "reject") {
       Reject();
@@ -211,6 +212,11 @@
         const std::string& nick = words[1];
         hangout_pubsub_client_->RemoteMute(nick);
       }
+    } else if ((command == "block") && (words.size() == 2)) {
+      if (InMuc()) {
+        const std::string& nick = words[1];
+        hangout_pubsub_client_->BlockMedia(nick);
+      }
     } else if ((command == "dtmf") && (words.size() == 2)) {
       int ev = std::string("0123456789*#").find(words[1][0]);
       call_->PressDTMF(ev);
@@ -256,19 +262,22 @@
       LeaveMuc(GetWord(words, 1, ""));
     } else if (command == "nick") {
       SetNick(GetWord(words, 1, ""));
+    } else if (command == "priority") {
+      int priority = GetInt(words, 1, 0);
+      SetPriority(priority);
+      SendStatus();
     } else if (command == "getdevs") {
       GetDevices();
     } else if ((words.size() == 2) && (command == "setvol")) {
       SetVolume(words[1]);
-    } else if (command == "voicemail") {
-      CallVoicemail((words.size() >= 2) ? words[1] : "");
     } else {
       console_->PrintLine(CONSOLE_COMMANDS);
     }
   }
 }
 
-CallClient::CallClient(buzz::XmppClient* xmpp_client)
+CallClient::CallClient(buzz::XmppClient* xmpp_client,
+                       const std::string& caps_node, const std::string& version)
     : xmpp_client_(xmpp_client),
       worker_thread_(NULL),
       media_engine_(NULL),
@@ -288,6 +297,8 @@
       initial_protocol_(cricket::PROTOCOL_HYBRID),
       secure_policy_(cricket::SEC_DISABLED) {
   xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange);
+  my_status_.set_caps_node(caps_node);
+  my_status_.set_version(version);
 }
 
 CallClient::~CallClient() {
@@ -431,8 +442,8 @@
 
 void CallClient::OnCallCreate(cricket::Call* call) {
   call->SignalSessionState.connect(this, &CallClient::OnSessionState);
-  call->SignalMediaSourcesUpdate.connect(
-      this, &CallClient::OnMediaSourcesUpdate);
+  call->SignalMediaStreamsUpdate.connect(
+      this, &CallClient::OnMediaStreamsUpdate);
 }
 
 void CallClient::OnSessionState(cricket::Call* call,
@@ -452,6 +463,7 @@
     }
     cricket::CallOptions options;
     if (auto_accept_) {
+      options.has_video = true;
       Accept(options);
     }
   } else if (state == cricket::Session::STATE_SENTINITIATE) {
@@ -477,20 +489,38 @@
 
 void CallClient::OnSpeakerChanged(cricket::Call* call,
                                   cricket::Session* session,
-                                  const cricket::NamedSource& speaker) {
-  if (speaker.ssrc == 0) {
+                                  const cricket::StreamParams& speaker) {
+  if (!speaker.has_ssrcs()) {
     console_->PrintLine("Session %s has no current speaker.",
                         session->id().c_str());
   } else if (speaker.nick.empty()) {
     console_->PrintLine("Session %s speaker change to unknown (%u).",
-                        session->id().c_str(), speaker.ssrc);
+                        session->id().c_str(), speaker.first_ssrc());
   } else {
     console_->PrintLine("Session %s speaker changed to %s (%u).",
                         session->id().c_str(), speaker.nick.c_str(),
-                        speaker.ssrc);
+                        speaker.first_ssrc());
   }
 }
 
+void SetMediaCaps(int media_caps, buzz::Status* status) {
+  status->set_voice_capability((media_caps & cricket::AUDIO_RECV) != 0);
+  status->set_video_capability((media_caps & cricket::VIDEO_RECV) != 0);
+  status->set_camera_capability((media_caps & cricket::VIDEO_SEND) != 0);
+}
+
+void SetCaps(int media_caps, buzz::Status* status) {
+  status->set_know_capabilities(true);
+  status->set_pmuc_capability(true);
+  SetMediaCaps(media_caps, status);
+}
+
+void SetAvailable(const buzz::Jid& jid, buzz::Status* status) {
+  status->set_jid(jid);
+  status->set_available(true);
+  status->set_show(buzz::Status::SHOW_ONLINE);
+}
+
 void CallClient::InitPresence() {
   presence_push_ = new buzz::PresencePushTask(xmpp_client_, this);
   presence_push_->SignalStatusUpdate.connect(
@@ -502,7 +532,9 @@
   presence_push_->Start();
 
   presence_out_ = new buzz::PresenceOutTask(xmpp_client_);
-  RefreshStatus();
+  SetAvailable(xmpp_client_->jid(), &my_status_);
+  SetCaps(media_client_->GetCapabilities(), &my_status_);
+  SendStatus(my_status_);
   presence_out_->Start();
 
   muc_invite_recv_ = new buzz::MucInviteRecvTask(xmpp_client_);
@@ -517,23 +549,8 @@
   friend_invite_send_->Start();
 }
 
-void CallClient::RefreshStatus() {
-  int media_caps = media_client_->GetCapabilities();
-  my_status_.set_jid(xmpp_client_->jid());
-  my_status_.set_available(true);
-  my_status_.set_show(buzz::Status::SHOW_ONLINE);
-  my_status_.set_priority(0);
-  my_status_.set_know_capabilities(true);
-  my_status_.set_pmuc_capability(true);
-  my_status_.set_voice_capability(
-      (media_caps & cricket::AUDIO_RECV) != 0);
-  my_status_.set_video_capability(
-      (media_caps & cricket::VIDEO_RECV) != 0);
-  my_status_.set_camera_capability(
-      (media_caps & cricket::VIDEO_SEND) != 0);
-  my_status_.set_is_google_client(true);
-  my_status_.set_version("1.0.0.67");
-  presence_out_->Send(my_status_);
+void CallClient::SendStatus(const buzz::Status& status) {
+  presence_out_->Send(status);
 }
 
 void CallClient::OnStatusUpdate(const buzz::Status& status) {
@@ -613,10 +630,6 @@
     // if the first character is a +, assume it's a phone number
     found_jid = callto_jid;
     found = true;
-  } else if (callto_jid.resource() == "voicemail") {
-    // if the resource is /voicemail, allow that
-    found_jid = callto_jid;
-    found = true;
   } else {
     // otherwise, it's a friend
     for (RosterMap::iterator iter = roster_->begin();
@@ -673,6 +686,8 @@
         this, &CallClient::OnRecordingStateChange);
     hangout_pubsub_client_->SignalRemoteMute.connect(
         this, &CallClient::OnRemoteMuted);
+    hangout_pubsub_client_->SignalMediaBlock.connect(
+        this, &CallClient::OnMediaBlocked);
     hangout_pubsub_client_->SignalRequestError.connect(
         this, &CallClient::OnHangoutRequestError);
     hangout_pubsub_client_->SignalPublishAudioMuteError.connect(
@@ -709,7 +724,7 @@
     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) {
+  } else if (was_recording && !is_recording) {
     console_->PrintLine("%s no longer recording.", nick.c_str());
   }
 }
@@ -726,6 +741,12 @@
   }
 }
 
+void CallClient::OnMediaBlocked(const std::string& blockee_nick,
+                                const std::string& blocker_nick) {
+  console_->PrintLine("%s blocked by %s.",
+                      blockee_nick.c_str(), blocker_nick.c_str());
+}
+
 void CallClient::OnHangoutRequestError(const std::string& node,
                                        const buzz::XmlElement* stanza) {
   console_->PrintLine("Failed request pub sub items for node %s.",
@@ -753,31 +774,6 @@
   console_->PrintLine("Failed to remote mute.");
 }
 
-void CallClient::CallVoicemail(const std::string& name) {
-  buzz::Jid jid(name);
-  if (!jid.IsValid() || jid.node() == "") {
-    console_->PrintLine("Invalid JID. JIDs should be in the form user@domain.");
-    return;
-  }
-  buzz::VoicemailJidRequester *request =
-      new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid());
-  request->SignalGotVoicemailJid.connect(this,
-                                         &CallClient::OnFoundVoicemailJid);
-  request->SignalVoicemailJidError.connect(this,
-                                           &CallClient::OnVoicemailJidError);
-  request->Start();
-}
-
-void CallClient::OnFoundVoicemailJid(const buzz::Jid& to,
-                                     const buzz::Jid& voicemail) {
-  console_->PrintLine("Calling %s's voicemail.", to.Str().c_str());
-  PlaceCall(voicemail, cricket::CallOptions());
-}
-
-void CallClient::OnVoicemailJidError(const buzz::Jid& to) {
-  console_->PrintLine("Unable to voicemail %s.", to.Str().c_str());
-}
-
 void CallClient::Accept(const cricket::CallOptions& options) {
   ASSERT(call_ && incoming_call_);
   ASSERT(call_->sessions().size() == 1);
@@ -832,9 +828,8 @@
     domain = room_name.substr(room_name.find("@") + 1);
   }
 
-  buzz::MucRoomLookupTask* lookup_query_task =
-      new buzz::MucRoomLookupTask(
-          xmpp_client_, buzz::JID_GOOGLE_MUC_LOOKUP, room, domain);
+  buzz::MucRoomLookupTask* lookup_query_task = new buzz::MucRoomLookupTask(
+      xmpp_client_, buzz::Jid(buzz::STR_GOOGLE_MUC_LOOKUP_JID), room, domain);
   lookup_query_task->SignalResult.connect(this,
       &CallClient::OnRoomLookupResponse);
   lookup_query_task->SignalError.connect(this,
@@ -984,20 +979,26 @@
 }
 
 bool CallClient::InMuc() {
-  return FirstMucJid().IsValid();
+  const buzz::Jid* muc_jid = FirstMucJid();
+  if (!muc_jid) return false;
+  return muc_jid->IsValid();
 }
 
-const buzz::Jid& CallClient::FirstMucJid() {
-  return mucs_.begin()->first;
+const buzz::Jid* CallClient::FirstMucJid() {
+  if (mucs_.empty()) return NULL;
+  return &(mucs_.begin()->first);
 }
 
 void CallClient::LeaveMuc(const std::string& room) {
   buzz::Jid room_jid;
+  const buzz::Jid* muc_jid = FirstMucJid();
   if (room.length() > 0) {
     room_jid = buzz::Jid(room);
   } else if (mucs_.size() > 0) {
     // leave the first MUC if no JID specified
-    room_jid = FirstMucJid();
+    if (muc_jid) {
+      room_jid = *(muc_jid);
+    }
   }
 
   if (!room_jid.IsValid()) {
@@ -1103,31 +1104,36 @@
 
 void CallClient::OnDevicesChange() {
   console_->PrintLine("Devices changed.");
-  RefreshStatus();
+  SetMediaCaps(media_client_->GetCapabilities(), &my_status_);
+  SendStatus(my_status_);
 }
 
 void CallClient::SetVolume(const std::string& level) {
   media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
 }
 
-void CallClient::OnMediaSourcesUpdate(cricket::Call* call,
+void CallClient::OnMediaStreamsUpdate(cricket::Call* call,
                                       cricket::Session* session,
-                                      const cricket::MediaSources& sources) {
-  for (cricket::NamedSources::const_iterator it = sources.video().begin();
-       it != sources.video().end(); ++it) {
-    if (it->removed) {
-      RemoveStaticRenderedView(it->ssrc);
-    } else {
-      if (render_) {
+                                      const cricket::MediaStreams& added,
+                                      const cricket::MediaStreams& removed) {
+  if (call->video()) {
+    for (std::vector<cricket::StreamParams>::const_iterator
+         it = removed.video().begin(); it != removed.video().end(); ++it) {
+      RemoveStaticRenderedView(it->first_ssrc());
+    }
+
+    if (render_) {
+      for (std::vector<cricket::StreamParams>::const_iterator
+           it = added.video().begin(); it != added.video().end(); ++it) {
         // TODO: Make dimensions and positions more configurable.
         int offset = (50 * static_views_accumulated_count_) % 300;
-        AddStaticRenderedView(session, it->ssrc, 640, 400, 30,
+        AddStaticRenderedView(session, it->first_ssrc(), 640, 400, 30,
                               offset, offset);
       }
     }
-  }
 
-  SendViewRequest(session);
+    SendViewRequest(session);
+  }
 }
 
 // TODO: Would these methods to add and remove views make
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
index 568a818..76184dc 100644
--- a/talk/examples/call/callclient.h
+++ b/talk/examples/call/callclient.h
@@ -47,7 +47,6 @@
 class MucInviteRecvTask;
 class MucInviteSendTask;
 class FriendInviteSendTask;
-class VoicemailJidRequester;
 class DiscoInfoQueryTask;
 class Muc;
 class Status;
@@ -74,7 +73,8 @@
 class Call;
 class SessionManagerTask;
 struct CallOptions;
-struct NamedSource;
+struct MediaStreams;
+struct StreamParams;
 }
 
 struct RosterItem {
@@ -98,7 +98,9 @@
 
 class CallClient: public sigslot::has_slots<> {
  public:
-  explicit CallClient(buzz::XmppClient* xmpp_client);
+  CallClient(buzz::XmppClient* xmpp_client,
+             const std::string& caps_node,
+             const std::string& version);
   ~CallClient();
 
   cricket::MediaSessionClient* media_client() const { return media_client_; }
@@ -117,6 +119,13 @@
   void SetConsole(Console *console) {
     console_ = console;
   }
+  void SetPriority(int priority) {
+    my_status_.set_priority(priority);
+  }
+  void SendStatus() {
+    SendStatus(my_status_);
+  }
+  void SendStatus(const buzz::Status& status);
 
   void ParseLine(const std::string &str);
 
@@ -127,7 +136,7 @@
   void LookupAndJoinMuc(const std::string& room_name);
   void InviteToMuc(const std::string& user, const std::string& room);
   bool InMuc();
-  const buzz::Jid& FirstMucJid();
+  const buzz::Jid* FirstMucJid();
   void LeaveMuc(const std::string& room);
   void SetNick(const std::string& muc_nick);
   void SetPortAllocatorFlags(uint32 flags) { portallocator_flags_ = flags; }
@@ -157,7 +166,6 @@
 
   void InitMedia();
   void InitPresence();
-  void RefreshStatus();
   void OnRequestSignaling();
   void OnSessionCreate(cricket::Session* session, bool initiate);
   void OnCallCreate(cricket::Call* call);
@@ -180,6 +188,8 @@
   void OnRemoteMuted(const std::string& mutee_nick,
                      const std::string& muter_nick,
                      bool should_mute_locally);
+  void OnMediaBlocked(const std::string& blockee_nick,
+                      const std::string& blocker_nick);
   void OnHangoutRequestError(const std::string& node,
                              const buzz::XmlElement* stanza);
   void OnHangoutPublishAudioMuteError(const std::string& task_id,
@@ -192,14 +202,13 @@
                                 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);
-  void OnMediaSourcesUpdate(cricket::Call* call,
+  void OnMediaStreamsUpdate(cricket::Call* call,
                             cricket::Session* session,
-                            const cricket::MediaSources& sources);
+                            const cricket::MediaStreams& added,
+                            const cricket::MediaStreams& removed);
   void OnSpeakerChanged(cricket::Call* call,
                         cricket::Session* session,
-                        const cricket::NamedSource& speaker_source);
+                        const cricket::StreamParams& speaker_stream);
   void OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
                             const buzz::MucRoomInfo& room_info);
   void OnRoomLookupError(buzz::IqTask* task,
@@ -223,7 +232,6 @@
   void PrintRoster();
   void MakeCallTo(const std::string& name, const cricket::CallOptions& options);
   void PlaceCall(const buzz::Jid& jid, const cricket::CallOptions& options);
-  void CallVoicemail(const std::string& name);
   void Accept(const cricket::CallOptions& options);
   void Reject();
   void Quit();
diff --git a/talk/examples/call/callclient_unittest.cc b/talk/examples/call/callclient_unittest.cc
new file mode 100644
index 0000000..0c2fd95
--- /dev/null
+++ b/talk/examples/call/callclient_unittest.cc
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Unit tests for CallClient
+
+#include "talk/base/gunit.h"
+#include "talk/examples/call/callclient.h"
+#include "talk/examples/login/xmppthread.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/filemediaengine.h"
+
+TEST(CallClientTest, CreateCallClientWithDefaultMediaEngine) {
+  XmppPump pump;
+  CallClient *client = new CallClient(pump.client(), "app", "version");
+  delete client;
+}
+
+TEST(CallClientTest, CreateCallClientWithFileMediaEngine) {
+  XmppPump pump;
+  CallClient *client = new CallClient(pump.client(), "app", "version");
+  client->SetMediaEngine(new cricket::FileMediaEngine);
+  delete client;
+}
diff --git a/talk/examples/call/mediaenginefactory.cc b/talk/examples/call/mediaenginefactory.cc
new file mode 100644
index 0000000..7bd39c9
--- /dev/null
+++ b/talk/examples/call/mediaenginefactory.cc
@@ -0,0 +1,82 @@
+//
+// libjingle
+// Copyright 2004--2007, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "talk/examples/call/mediaenginefactory.h"
+
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/filemediaengine.h"
+
+std::vector<cricket::AudioCodec> RequiredAudioCodecs() {
+  std::vector<cricket::AudioCodec> audio_codecs;
+  audio_codecs.push_back(
+      cricket::AudioCodec(9, "G722", 16000, 0, 1, 0));
+  audio_codecs.push_back(
+      cricket::AudioCodec(0, "PCMU", 8000, 0, 1, 0));
+  audio_codecs.push_back(
+      cricket::AudioCodec(13, "CN", 8000, 0, 1, 0));
+  audio_codecs.push_back(
+      cricket::AudioCodec(105, "CN", 16000, 0, 1, 0));
+  return audio_codecs;
+}
+
+std::vector<cricket::VideoCodec> RequiredVideoCodecs() {
+  std::vector<cricket::VideoCodec> video_codecs;
+  video_codecs.push_back(
+      cricket::VideoCodec(97, "H264", 320, 240, 30, 0));
+  video_codecs.push_back(
+      cricket::VideoCodec(99, "H264-SVC", 640, 360, 30, 0));
+  return video_codecs;
+}
+
+cricket::MediaEngineInterface* MediaEngineFactory::CreateFileMediaEngine(
+    const char* voice_in, const char* voice_out,
+    const char* video_in, const char* video_out) {
+
+  cricket::FileMediaEngine* file_media_engine = new cricket::FileMediaEngine;
+  // Set the RTP dump file names.
+  if (voice_in) {
+    file_media_engine->set_voice_input_filename(voice_in);
+  }
+  if (voice_out) {
+    file_media_engine->set_voice_output_filename(voice_out);
+  }
+  if (video_in) {
+    file_media_engine->set_video_input_filename(video_in);
+  }
+  if (video_out) {
+    file_media_engine->set_video_output_filename(video_out);
+  }
+
+  // Set voice and video codecs. TODO: The codecs actually depend on
+  // the the input voice and video streams.
+  file_media_engine->set_voice_codecs(RequiredAudioCodecs());
+  file_media_engine->set_video_codecs(RequiredVideoCodecs());
+
+  return file_media_engine;
+}
diff --git a/talk/examples/call/mediaenginefactory.h b/talk/examples/call/mediaenginefactory.h
new file mode 100644
index 0000000..f7ba68e
--- /dev/null
+++ b/talk/examples/call/mediaenginefactory.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_CALL_MEDIAENGINEFACTORY_H_
+#define TALK_EXAMPLES_CALL_MEDIAENGINEFACTORY_H_
+
+#include "talk/session/phone/mediaengine.h"
+
+class MediaEngineFactory {
+ public:
+  static cricket::MediaEngineInterface* CreateFileMediaEngine(
+      const char* voice_in, const char* voice_out,
+      const char* video_in, const char* video_out);
+};
+
+#endif  // TALK_EXAMPLES_CALL_MEDIAENGINEFACTORY_H_
diff --git a/talk/examples/call/presenceouttask.cc b/talk/examples/call/presenceouttask.cc
index 919a1af..222036f 100644
--- a/talk/examples/call/presenceouttask.cc
+++ b/talk/examples/call/presenceouttask.cc
@@ -39,7 +39,9 @@
   if (GetState() != STATE_INIT && GetState() != STATE_START)
     return XMPP_RETURN_BADSTATE;
 
-  QueueStanza(TranslateStatus(s));
+  XmlElement * presence = TranslateStatus(s);
+  QueueStanza(presence);
+  delete presence;
   return XMPP_RETURN_OK;
 }
 
@@ -51,6 +53,7 @@
   XmlElement * presence = TranslateStatus(s);
   presence->AddAttr(QN_TO, j.Str());
   QueueStanza(presence);
+  delete presence;
   return XMPP_RETURN_OK;
 }
 
@@ -63,6 +66,7 @@
   presence->AddAttr(QN_TYPE, "probe");
 
   QueueStanza(presence);
+  delete presence;
   return XMPP_RETURN_OK;
 }
 
@@ -117,9 +121,9 @@
     result->AddElement(new XmlElement(QN_PRIORITY));
     result->AddText(pri, 1);
 
-    if (s.know_capabilities() && s.is_google_client()) {
+    if (s.know_capabilities()) {
       result->AddElement(new XmlElement(QN_CAPS_C, true));
-      result->AddAttr(QN_NODE, GOOGLE_CLIENT_NODE, 1);
+      result->AddAttr(QN_NODE, s.caps_node(), 1);
       result->AddAttr(QN_VER, s.version(), 1);
 
       std::string caps;
diff --git a/talk/examples/call/presencepushtask.cc b/talk/examples/call/presencepushtask.cc
index fba3322..97e0d35 100644
--- a/talk/examples/call/presencepushtask.cc
+++ b/talk/examples/call/presencepushtask.cc
@@ -187,11 +187,8 @@
       std::string exts = caps->Attr(QN_EXT);
 
       s->set_know_capabilities(true);
-
-      if (node == GOOGLE_CLIENT_NODE) {
-        s->set_is_google_client(true);
-        s->set_version(ver);
-      }
+      s->set_caps_node(node);
+      s->set_version(ver);
 
       if (ListContainsToken(exts, "voice-v1")) {
         s->set_voice_capability(true);
diff --git a/talk/examples/call/status.h b/talk/examples/call/status.h
index c3778e5..fb8fbc8 100644
--- a/talk/examples/call/status.h
+++ b/talk/examples/call/status.h
@@ -31,24 +31,22 @@
 #include "talk/xmpp/jid.h"
 #include "talk/xmpp/constants.h"
 
-#define GOOGLE_CLIENT_NODE "http://www.google.com/xmpp/client/caps"
-
 namespace buzz {
 
 class Status {
 public:
-  Status() :
-    pri_(0),
-    show_(SHOW_NONE),
-    available_(false),
-    e_code_(0),
-    feedback_probation_(false),
-    know_capabilities_(false),
-    voice_capability_(false),
-    pmuc_capability_(false),
-    video_capability_(false),
-    camera_capability_(false),
-    is_google_client_(false) {}
+  Status()
+      : pri_(0),
+        show_(SHOW_NONE),
+        available_(false),
+        e_code_(0),
+        feedback_probation_(false),
+        know_capabilities_(false),
+        voice_capability_(false),
+        pmuc_capability_(false),
+        video_capability_(false),
+        camera_capability_(false) {
+  }
 
   ~Status() {}
 
@@ -66,29 +64,29 @@
     SHOW_CHAT     = 6,
   };
 
-  const Jid & jid() const { return jid_; }
+  const Jid& jid() const { return jid_; }
   int priority() const { return pri_; }
   Show show() const { return show_; }
-  const std::string & status() const { return status_; }
-  const std::string & nick() const { return nick_; }
+  const std::string& status() const { return status_; }
+  const std::string& nick() const { return nick_; }
   bool available() const { return available_ ; }
   int error_code() const { return e_code_; }
-  const std::string & error_string() const { return e_str_; }
+  const std::string& error_string() const { return e_str_; }
   bool know_capabilities() const { return know_capabilities_; }
   bool voice_capability() const { return voice_capability_; }
   bool pmuc_capability() const { return pmuc_capability_; }
   bool video_capability() const { return video_capability_; }
   bool camera_capability() const { return camera_capability_; }
-  bool is_google_client() const { return is_google_client_; }
-  const std::string & version() const { return version_; }
+  const std::string& caps_node() const { return caps_node_; }
+  const std::string& version() const { return version_; }
   bool feedback_probation() const { return feedback_probation_; }
   const std::string& sent_time() const { return sent_time_; }
 
-  void set_jid(const Jid & jid) { jid_ = jid; }
+  void set_jid(const Jid& jid) { jid_ = jid; }
   void set_priority(int pri) { pri_ = pri; }
   void set_show(Show show) { show_ = show; }
-  void set_status(const std::string & status) { status_ = status; }
-  void set_nick(const std::string & nick) { nick_ = nick; }
+  void set_status(const std::string& status) { status_ = status; }
+  void set_nick(const std::string& nick) { nick_ = nick; }
   void set_available(bool a) { available_ = a; }
   void set_error(int e_code, const std::string e_str)
       { e_code_ = e_code; e_str_ = e_str; }
@@ -97,22 +95,22 @@
   void set_pmuc_capability(bool f) { pmuc_capability_ = f; }
   void set_video_capability(bool f) { video_capability_ = f; }
   void set_camera_capability(bool f) { camera_capability_ = f; }
-  void set_is_google_client(bool f) { is_google_client_ = f; }
-  void set_version(const std::string & v) { version_ = v; }
+  void set_caps_node(const std::string& f) { caps_node_ = f; }
+  void set_version(const std::string& v) { version_ = v; }
   void set_feedback_probation(bool f) { feedback_probation_ = f; }
   void set_sent_time(const std::string& time) { sent_time_ = time; }
 
-  void UpdateWith(const Status & new_value) {
+  void UpdateWith(const Status& new_value) {
     if (!new_value.know_capabilities()) {
        bool k = know_capabilities();
-       bool i = is_google_client();
        bool p = voice_capability();
+       std::string node = caps_node();
        std::string v = version();
 
        *this = new_value;
 
        set_know_capabilities(k);
-       set_is_google_client(i);
+       set_caps_node(node);
        set_voice_capability(p);
        set_version(v);
     }
@@ -175,7 +173,7 @@
     return result;
   }
 
-  static std::string TrimStatus(const std::string & st) {
+  static std::string TrimStatus(const std::string& st) {
     std::string s(st);
     int j = 0;
     bool collapsing = true;
@@ -220,7 +218,7 @@
   bool pmuc_capability_;
   bool video_capability_;
   bool camera_capability_;
-  bool is_google_client_;
+  std::string caps_node_;
   std::string version_;
 
   std::string sent_time_; // from the jabber:x:delay element
diff --git a/talk/examples/login/autoportallocator.h b/talk/examples/login/autoportallocator.h
new file mode 100644
index 0000000..7e0b062
--- /dev/null
+++ b/talk/examples/login/autoportallocator.h
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_LOGIN_AUTOPORTALLOCATOR_H_
+#define TALK_EXAMPLES_LOGIN_AUTOPORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/examples/login/jingleinfotask.h"
+#include "talk/base/sigslot.h"
+#include "talk/p2p/client/httpportallocator.h"
+#include "talk/xmpp/xmppclient.h"
+
+// This class sets the relay and stun servers using XmppClient.
+// It enables the client to traverse Proxy and NAT.
+class AutoPortAllocator : public cricket::HttpPortAllocator {
+ public:
+  AutoPortAllocator(talk_base::NetworkManager* network_manager,
+                    const std::string& user_agent)
+      : cricket::HttpPortAllocator(network_manager, user_agent) {
+  }
+
+  // Creates and initiates a task to get relay token from XmppClient and set
+  // it appropriately.
+  void SetXmppClient(buzz::XmppClient* client) {
+    // The JingleInfoTask is freed by the task-runner.
+    buzz::JingleInfoTask* jit = new buzz::JingleInfoTask(client);
+    jit->SignalJingleInfo.connect(this, &AutoPortAllocator::OnJingleInfo);
+    jit->Start();
+    jit->RefreshJingleInfoNow();
+  }
+
+ private:
+  void OnJingleInfo(
+      const std::string& token,
+      const std::vector<std::string>& relay_hosts,
+      const std::vector<talk_base::SocketAddress>& stun_hosts) {
+    SetRelayToken(token);
+    SetStunHosts(stun_hosts);
+    SetRelayHosts(relay_hosts);
+  }
+};
+
+#endif  // TALK_EXAMPLES_LOGIN_AUTOPORTALLOCATOR_H_
diff --git a/talk/examples/login/jingleinfotask.cc b/talk/examples/login/jingleinfotask.cc
new file mode 100644
index 0000000..c8932cd
--- /dev/null
+++ b/talk/examples/login/jingleinfotask.cc
@@ -0,0 +1,137 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/login/jingleinfotask.h"
+#include "talk/base/socketaddress.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class JingleInfoTask::JingleInfoGetTask : public XmppTask {
+ public:
+  explicit JingleInfoGetTask(XmppTaskParentInterface* parent)
+      : XmppTask(parent, XmppEngine::HL_SINGLE),
+        done_(false) {}
+
+  virtual int ProcessStart() {
+    talk_base::scoped_ptr<XmlElement> get(
+        MakeIq(STR_GET, Jid(), task_id()));
+    get->AddElement(new XmlElement(QN_JINGLE_INFO_QUERY, true));
+    if (SendStanza(get.get()) != XMPP_RETURN_OK) {
+      return STATE_ERROR;
+    }
+    return STATE_RESPONSE;
+  }
+  virtual int ProcessResponse() {
+    if (done_)
+      return STATE_DONE;
+    return STATE_BLOCKED;
+  }
+
+ protected:
+  virtual bool HandleStanza(const XmlElement * stanza) {
+    if (!MatchResponseIq(stanza, Jid(), task_id()))
+      return false;
+
+    if (stanza->Attr(QN_TYPE) != STR_RESULT)
+      return false;
+
+    // Queue the stanza with the parent so these don't get handled out of order
+    JingleInfoTask* parent = static_cast<JingleInfoTask*>(GetParent());
+    parent->QueueStanza(stanza);
+
+    // Wake ourselves so we can go into the done state
+    done_ = true;
+    Wake();
+    return true;
+  }
+
+  bool done_;
+};
+
+
+void JingleInfoTask::RefreshJingleInfoNow() {
+  JingleInfoGetTask* get_task = new JingleInfoGetTask(this);
+  get_task->Start();
+}
+
+bool
+JingleInfoTask::HandleStanza(const XmlElement * stanza) {
+  if (!MatchRequestIq(stanza, "set", QN_JINGLE_INFO_QUERY))
+    return false;
+
+  // only respect relay push from the server
+  Jid from(stanza->Attr(QN_FROM));
+  if (!from.IsEmpty() &&
+      !from.BareEquals(GetClient()->jid()) &&
+      from != Jid(GetClient()->jid().domain()))
+    return false;
+
+  QueueStanza(stanza);
+  return true;
+}
+
+int
+JingleInfoTask::ProcessStart() {
+  std::vector<std::string> relay_hosts;
+  std::vector<talk_base::SocketAddress> stun_hosts;
+  std::string relay_token;
+  const XmlElement * stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+  const XmlElement * query = stanza->FirstNamed(QN_JINGLE_INFO_QUERY);
+  if (query == NULL)
+    return STATE_START;
+  const XmlElement *stun = query->FirstNamed(QN_JINGLE_INFO_STUN);
+  if (stun) {
+    for (const XmlElement *server = stun->FirstNamed(QN_JINGLE_INFO_SERVER);
+         server != NULL; server = server->NextNamed(QN_JINGLE_INFO_SERVER)) {
+      std::string host = server->Attr(QN_JINGLE_INFO_HOST);
+      std::string port = server->Attr(QN_JINGLE_INFO_UDP);
+      if (host != STR_EMPTY && host != STR_EMPTY) {
+        stun_hosts.push_back(talk_base::SocketAddress(host, atoi(port.c_str())));
+      }
+    }
+  }
+
+  const XmlElement *relay = query->FirstNamed(QN_JINGLE_INFO_RELAY);
+  if (relay) {
+    relay_token = relay->TextNamed(QN_JINGLE_INFO_TOKEN);
+    for (const XmlElement *server = relay->FirstNamed(QN_JINGLE_INFO_SERVER);
+         server != NULL; server = server->NextNamed(QN_JINGLE_INFO_SERVER)) {
+      std::string host = server->Attr(QN_JINGLE_INFO_HOST);
+      if (host != STR_EMPTY) {
+        relay_hosts.push_back(host);
+      }
+    }
+  }
+  SignalJingleInfo(relay_token, relay_hosts, stun_hosts);
+  return STATE_START;
+}
+}
diff --git a/talk/examples/login/jingleinfotask.h b/talk/examples/login/jingleinfotask.h
new file mode 100644
index 0000000..dbc3fb0
--- /dev/null
+++ b/talk/examples/login/jingleinfotask.h
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_LOGIN_JINGLEINFOTASK_H_
+#define TALK_EXAMPLES_LOGIN_JINGLEINFOTASK_H_
+
+#include <vector>
+
+#include "talk/p2p/client/httpportallocator.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/base/sigslot.h"
+
+namespace buzz {
+
+class JingleInfoTask : public XmppTask {
+ public:
+  explicit JingleInfoTask(XmppTaskParentInterface* parent) :
+    XmppTask(parent, XmppEngine::HL_TYPE) {}
+
+  virtual int ProcessStart();
+  void RefreshJingleInfoNow();
+
+  sigslot::signal3<const std::string &,
+                   const std::vector<std::string> &,
+                   const std::vector<talk_base::SocketAddress> &>
+                       SignalJingleInfo;
+
+ protected:
+  class JingleInfoGetTask;
+  friend class JingleInfoGetTask;
+
+  virtual bool HandleStanza(const XmlElement * stanza);
+};
+}
+
+#endif  // TALK_EXAMPLES_LOGIN_JINGLEINFOTASK_H_
diff --git a/talk/examples/login/login_main.cc b/talk/examples/login/login_main.cc
index 186020a..9cd00b9 100644
--- a/talk/examples/login/login_main.cc
+++ b/talk/examples/login/login_main.cc
@@ -31,6 +31,7 @@
 #include "talk/base/thread.h"
 #include "talk/xmpp/xmppclientsettings.h"
 #include "talk/examples/login/xmppthread.h"
+#include "talk/xmpp/xmppengine.h"
 
 int main(int argc, char **argv) {
   std::cout << "Auth Cookie: ";
@@ -48,7 +49,7 @@
   buzz::XmppClientSettings xcs;
   xcs.set_user(username.c_str());
   xcs.set_host("gmail.com");
-  xcs.set_use_tls(false);
+  xcs.set_use_tls(buzz::TLS_DISABLED);
   xcs.set_auth_cookie(auth_cookie.c_str());
   xcs.set_server(talk_base::SocketAddress("talk.google.com", 5222));
   thread.Login(xcs);
diff --git a/talk/examples/login/xmppauth.cc b/talk/examples/login/xmppauth.cc
index 3551b97..7773e00 100644
--- a/talk/examples/login/xmppauth.cc
+++ b/talk/examples/login/xmppauth.cc
@@ -2,26 +2,26 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
@@ -37,7 +37,7 @@
 
 XmppAuth::~XmppAuth() {
 }
-  
+
 void XmppAuth::StartPreXmppAuth(const buzz::Jid & jid,
                                 const talk_base::SocketAddress & server,
                                 const talk_base::CryptString & pass,
@@ -49,25 +49,26 @@
 
   SignalAuthDone();
 }
-  
+
 std::string XmppAuth::ChooseBestSaslMechanism(
     const std::vector<std::string> & mechanisms,
     bool encrypted) {
   std::vector<std::string>::const_iterator it;
 
-  // a token is the weakest auth - 15s, service-limited, so prefer it.
+  // A token is the weakest auth - 15s, service-limited, so prefer it.
   it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-TOKEN");
   if (it != mechanisms.end() && !auth_cookie_.empty())
     return "X-GOOGLE-TOKEN";
 
-  // a cookie is the next weakest - 14 days
+  // A cookie is the next weakest - 14 days.
   it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-COOKIE");
   if (it != mechanisms.end() && !auth_cookie_.empty())
     return "X-GOOGLE-COOKIE";
 
-    it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
-    if (it != mechanisms.end())
-      return "PLAIN";
+  // As a last resort, use plain authentication.
+  it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
+  if (it != mechanisms.end())
+    return "PLAIN";
 
   // No good mechanism found
  return "";
diff --git a/talk/examples/login/xmpppump.h b/talk/examples/login/xmpppump.h
index 4e79748..6f705ec 100644
--- a/talk/examples/login/xmpppump.h
+++ b/talk/examples/login/xmpppump.h
@@ -31,7 +31,7 @@
 #include "talk/base/messagequeue.h"
 #include "talk/base/taskrunner.h"
 #include "talk/base/thread.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 #include "talk/xmpp/xmppclient.h"
 #include "talk/xmpp/xmppengine.h"
 #include "talk/xmpp/xmpptask.h"
diff --git a/talk/examples/login/xmppsocket.cc b/talk/examples/login/xmppsocket.cc
index 75325e4..cbd4661 100644
--- a/talk/examples/login/xmppsocket.cc
+++ b/talk/examples/login/xmppsocket.cc
@@ -45,13 +45,13 @@
 #endif  // FEATURE_ENABLE_SSL
 #endif  // USE_SSLSTREAM
 
-XmppSocket::XmppSocket(bool tls) : tls_(tls) {
+XmppSocket::XmppSocket(buzz::TlsOptions tls) : tls_(tls) {
   talk_base::Thread* pth = talk_base::Thread::Current();
   talk_base::AsyncSocket* socket =
     pth->socketserver()->CreateAsyncSocket(SOCK_STREAM);
 #ifndef USE_SSLSTREAM
 #ifdef FEATURE_ENABLE_SSL
-  if (tls_) {
+  if (tls_ != buzz::TLS_DISABLED) {
     socket = talk_base::SSLAdapter::Create(socket);
   }
 #endif  // FEATURE_ENABLE_SSL
@@ -65,7 +65,7 @@
   cricket_socket_ = socket;
   stream_ = new talk_base::SocketStream(cricket_socket_);
 #ifdef FEATURE_ENABLE_SSL
-  if (tls_)
+  if (tls_ != buzz::TLS_DISABLED)
     stream_ = talk_base::SSLStreamAdapter::Create(stream_);
 #endif  // FEATURE_ENABLE_SSL
   stream_->SignalEvent.connect(this, &XmppSocket::OnEvent);
@@ -226,7 +226,7 @@
 
 bool XmppSocket::StartTls(const std::string & domainname) {
 #if defined(FEATURE_ENABLE_SSL)
-  if (!tls_)
+  if (tls_ == buzz::TLS_DISABLED)
     return false;
 #ifndef USE_SSLSTREAM
   talk_base::SSLAdapter* ssl_adapter =
diff --git a/talk/examples/login/xmppsocket.h b/talk/examples/login/xmppsocket.h
index bce8ee3..ed9f6e1 100644
--- a/talk/examples/login/xmppsocket.h
+++ b/talk/examples/login/xmppsocket.h
@@ -32,6 +32,7 @@
 #include "talk/base/bytebuffer.h"
 #include "talk/base/sigslot.h"
 #include "talk/xmpp/asyncsocket.h"
+#include "talk/xmpp/xmppengine.h"
 
 // The below define selects the SSLStreamAdapter implementation for
 // SSL, as opposed to the SSLAdapter socket adapter.
@@ -44,7 +45,7 @@
 
 class XmppSocket : public buzz::AsyncSocket, public sigslot::has_slots<> {
 public:
-  XmppSocket(bool tls);
+  XmppSocket(buzz::TlsOptions tls);
   ~XmppSocket();
 
   virtual buzz::AsyncSocket::State state();
@@ -75,7 +76,7 @@
 #endif  // USE_SSLSTREAM
   buzz::AsyncSocket::State state_;
   talk_base::ByteBuffer buffer_;
-  bool tls_;
+  buzz::TlsOptions tls_;
 };
 
 #endif // _XMPPSOCKET_H_
diff --git a/talk/examples/login/xmppthread.cc b/talk/examples/login/xmppthread.cc
index c030391..5345643 100644
--- a/talk/examples/login/xmppthread.cc
+++ b/talk/examples/login/xmppthread.cc
@@ -25,8 +25,9 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "talk/xmpp/xmppclientsettings.h"
 #include "talk/examples/login/xmppthread.h"
+
+#include "talk/xmpp/xmppclientsettings.h"
 #include "talk/examples/login/xmppauth.h"
 
 namespace {
@@ -70,7 +71,8 @@
   if (pmsg->message_id == MSG_LOGIN) {
     ASSERT(pmsg->pdata != NULL);
     LoginData* data = reinterpret_cast<LoginData*>(pmsg->pdata);
-    pump_->DoLogin(data->xcs, new XmppSocket(false), new XmppAuth());
+    pump_->DoLogin(data->xcs, new XmppSocket(buzz::TLS_DISABLED),
+        new XmppAuth());
     delete data;
   } else if (pmsg->message_id == MSG_DISCONNECT) {
     pump_->DoDisconnect();
diff --git a/talk/examples/login/xmppthread.h b/talk/examples/login/xmppthread.h
index bfed5e0..efaa67a 100644
--- a/talk/examples/login/xmppthread.h
+++ b/talk/examples/login/xmppthread.h
@@ -25,13 +25,15 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef _XMPPTHREAD_H_
-#define _XMPPTHREAD_H_
+#ifndef TALK_EXAMPLES_LOGIN_XMPPTHREAD_H_
+#define TALK_EXAMPLES_LOGIN_XMPPTHREAD_H_
 
-#include "talk/xmpp/xmppclientsettings.h"
 #include "talk/base/thread.h"
 #include "talk/examples/login/xmpppump.h"
 #include "talk/examples/login/xmppsocket.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppengine.h"
+
 
 class XmppThread:
     public talk_base::Thread, XmppPumpNotify, talk_base::MessageHandler {
@@ -53,4 +55,4 @@
   void OnMessage(talk_base::Message* pmsg);
 };
 
-#endif // _XMPPTHREAD_H_
+#endif  // TALK_EXAMPLES_LOGIN_XMPPTHREAD_H_
diff --git a/talk/examples/pcp/pcp_main.cc b/talk/examples/pcp/pcp_main.cc
new file mode 100644
index 0000000..3ec263f
--- /dev/null
+++ b/talk/examples/pcp/pcp_main.cc
@@ -0,0 +1,709 @@
+#define _CRT_SECURE_NO_DEPRECATE 1
+
+#include <time.h>
+
+#include <iomanip>
+#include <iostream>
+#include <string>
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#if HAVE_OPENSSL_SSL_H
+#define USE_SSL_TUNNEL
+#endif
+
+#include "talk/base/basicdefs.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/examples/login/autoportallocator.h"
+#include "talk/examples/login/xmpppump.h"
+#include "talk/examples/login/xmppsocket.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/client/sessionmanagertask.h"
+#include "talk/xmpp/xmppengine.h"
+#ifdef USE_SSL_TUNNEL
+#include "talk/session/tunnel/securetunnelsessionclient.h"
+#endif
+#include "talk/session/tunnel/tunnelsessionclient.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+#ifndef MAX_PATH
+#define MAX_PATH 256
+#endif
+
+#if defined(_MSC_VER) && (_MSC_VER < 1400)
+// The following are necessary to properly link when compiling STL without
+// /EHsc, otherwise known as C++ exceptions.
+void __cdecl std::_Throw(const std::exception &) {}
+std::_Prhand std::_Raise_handler = 0;
+#endif
+
+enum {
+  MSG_LOGIN_COMPLETE = 1,
+  MSG_LOGIN_FAILED,
+  MSG_DONE,
+};
+
+buzz::Jid gUserJid;
+talk_base::InsecureCryptStringImpl gUserPass;
+std::string gXmppHost = "talk.google.com";
+int gXmppPort = 5222;
+buzz::TlsOptions gXmppUseTls = buzz::TLS_REQUIRED;
+
+class DebugLog : public sigslot::has_slots<> {
+public:
+  DebugLog() :
+    debug_input_buf_(NULL), debug_input_len_(0), debug_input_alloc_(0),
+    debug_output_buf_(NULL), debug_output_len_(0), debug_output_alloc_(0),
+    censor_password_(false)
+      {}
+  char * debug_input_buf_;
+  int debug_input_len_;
+  int debug_input_alloc_;
+  char * debug_output_buf_;
+  int debug_output_len_;
+  int debug_output_alloc_;
+  bool censor_password_;
+
+  void Input(const char * data, int len) {
+    if (debug_input_len_ + len > debug_input_alloc_) {
+      char * old_buf = debug_input_buf_;
+      debug_input_alloc_ = 4096;
+      while (debug_input_alloc_ < debug_input_len_ + len) {
+        debug_input_alloc_ *= 2;
+      }
+      debug_input_buf_ = new char[debug_input_alloc_];
+      memcpy(debug_input_buf_, old_buf, debug_input_len_);
+      delete[] old_buf;
+    }
+    memcpy(debug_input_buf_ + debug_input_len_, data, len);
+    debug_input_len_ += len;
+    DebugPrint(debug_input_buf_, &debug_input_len_, false);
+  }
+
+  void Output(const char * data, int len) {
+    if (debug_output_len_ + len > debug_output_alloc_) {
+      char * old_buf = debug_output_buf_;
+      debug_output_alloc_ = 4096;
+      while (debug_output_alloc_ < debug_output_len_ + len) {
+        debug_output_alloc_ *= 2;
+      }
+      debug_output_buf_ = new char[debug_output_alloc_];
+      memcpy(debug_output_buf_, old_buf, debug_output_len_);
+      delete[] old_buf;
+    }
+    memcpy(debug_output_buf_ + debug_output_len_, data, len);
+    debug_output_len_ += len;
+    DebugPrint(debug_output_buf_, &debug_output_len_, true);
+  }
+
+  static bool
+  IsAuthTag(const char * str, size_t len) {
+    if (str[0] == '<' && str[1] == 'a' &&
+                         str[2] == 'u' &&
+                         str[3] == 't' &&
+                         str[4] == 'h' &&
+                         str[5] <= ' ') {
+      std::string tag(str, len);
+
+      if (tag.find("mechanism") != std::string::npos)
+        return true;
+
+    }
+    return false;
+  }
+
+  void
+  DebugPrint(char * buf, int * plen, bool output) {
+    int len = *plen;
+    if (len > 0) {
+      time_t tim = time(NULL);
+      struct tm * now = localtime(&tim);
+      char *time_string = asctime(now);
+      if (time_string) {
+        size_t time_len = strlen(time_string);
+        if (time_len > 0) {
+          time_string[time_len-1] = 0;    // trim off terminating \n
+        }
+      }
+      LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>>>>>>>>>>" : "RECV <<<<<<<<<<<<<<<<<<<<<<<<<")
+        << " : " << time_string;
+
+      bool indent;
+      int start = 0, nest = 3;
+      for (int i = 0; i < len; i += 1) {
+        if (buf[i] == '>') {
+          if ((i > 0) && (buf[i-1] == '/')) {
+            indent = false;
+          } else if ((start + 1 < len) && (buf[start + 1] == '/')) {
+            indent = false;
+            nest -= 2;
+          } else {
+            indent = true;
+          }
+
+          // Output a tag
+          LOG(INFO) << std::setw(nest) << " " << std::string(buf + start, i + 1 - start);
+
+          if (indent)
+            nest += 2;
+
+          // Note if it's a PLAIN auth tag
+          if (IsAuthTag(buf + start, i + 1 - start)) {
+            censor_password_ = true;
+          }
+
+          // incr
+          start = i + 1;
+        }
+
+        if (buf[i] == '<' && start < i) {
+          if (censor_password_) {
+            LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##";
+            censor_password_ = false;
+          }
+          else {
+            LOG(INFO) << std::setw(nest) << " " << std::string(buf + start, i - start);
+          }
+          start = i;
+        }
+      }
+      len = len - start;
+      memcpy(buf, buf + start, len);
+      *plen = len;
+    }
+  }
+
+};
+
+static DebugLog debug_log_;
+
+// Prints out a usage message then exits.
+void Usage() {
+  std::cerr << "Usage:" << std::endl;
+  std::cerr << "  pcp [options] <my_jid>                             (server mode)" << std::endl;
+  std::cerr << "  pcp [options] <my_jid> <src_file> <dst_full_jid>:<dst_file> (client sending)" << std::endl;
+  std::cerr << "  pcp [options] <my_jid> <src_full_jid>:<src_file> <dst_file> (client rcv'ing)" << std::endl;
+  std::cerr << "           --verbose" << std::endl;
+  std::cerr << "           --xmpp-host=<host>" << std::endl;
+  std::cerr << "           --xmpp-port=<port>" << std::endl;
+  std::cerr << "           --xmpp-use-tls=(true|false)" << std::endl;
+  exit(1);
+}
+
+// Prints out an error message, a usage message, then exits.
+void Error(const std::string& msg) {
+  std::cerr << "error: " << msg << std::endl;
+  std::cerr << std::endl;
+  Usage();
+}
+
+void FatalError(const std::string& msg) {
+  std::cerr << "error: " << msg << std::endl;
+  std::cerr << std::endl;
+  exit(1);
+}
+
+// Determines whether the given string is an option.  If so, the name and
+// value are appended to the given strings.
+bool ParseArg(const char* arg, std::string* name, std::string* value) {
+  if (strncmp(arg, "--", 2) != 0)
+    return false;
+
+  const char* eq = strchr(arg + 2, '=');
+  if (eq) {
+    if (name)
+      name->append(arg + 2, eq);
+    if (value)
+      value->append(eq + 1, arg + strlen(arg));
+  } else {
+    if (name)
+      name->append(arg + 2, arg + strlen(arg));
+    if (value)
+      value->clear();
+  }
+
+  return true;
+}
+
+int ParseIntArg(const std::string& name, const std::string& value) {
+  char* end;
+  long val = strtol(value.c_str(), &end, 10);
+  if (*end != '\0')
+    Error(std::string("value of option ") + name + " must be an integer");
+  return static_cast<int>(val);
+}
+
+#ifdef WIN32
+#pragma warning(push)
+// disable "unreachable code" warning b/c it varies between dbg and opt
+#pragma warning(disable: 4702)
+#endif
+bool ParseBoolArg(const std::string& name, const std::string& value) {
+  if (value == "true")
+    return true;
+  else if (value == "false")
+    return false;
+  else {
+    Error(std::string("value of option ") + name + " must be true or false");
+    return false;
+  }
+}
+#ifdef WIN32
+#pragma warning(pop)
+#endif
+
+void ParseFileArg(const char* arg, buzz::Jid* jid, std::string* file) {
+  const char* sep = strchr(arg, ':');
+  if (!sep) {
+    *file = arg;
+  } else {
+    buzz::Jid jid_arg(std::string(arg, sep-arg));
+    if (jid_arg.IsBare())
+      Error("A full JID is required for the source or destination arguments.");
+    *jid = jid_arg;
+    *file = std::string(sep+1);
+  }
+}
+
+
+void SetConsoleEcho(bool on) {
+#ifdef WIN32
+  HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+  if ((hIn == INVALID_HANDLE_VALUE) || (hIn == NULL))
+    return;
+
+  DWORD mode;
+  if (!GetConsoleMode(hIn, &mode))
+    return;
+
+  if (on) {
+    mode = mode | ENABLE_ECHO_INPUT;
+  } else {
+    mode = mode & ~ENABLE_ECHO_INPUT;
+  }
+
+  SetConsoleMode(hIn, mode);
+#else
+  int re;
+  if (on)
+    re = system("stty echo");
+  else
+    re = system("stty -echo");
+  if (-1 == re)
+    return;
+#endif
+}
+
+// Fills in a settings object with the values from the arguments.
+buzz::XmppClientSettings LoginSettings() {
+  buzz::XmppClientSettings xcs;
+  xcs.set_user(gUserJid.node());
+  xcs.set_host(gUserJid.domain());
+  xcs.set_resource("pcp");
+  xcs.set_pass(talk_base::CryptString(gUserPass));
+  talk_base::SocketAddress server(gXmppHost, gXmppPort);
+  xcs.set_server(server);
+  xcs.set_use_tls(gXmppUseTls);
+  return xcs;
+}
+
+// Runs the current thread until a message with the given ID is seen.
+uint32 Loop(const std::vector<uint32>& ids) {
+  talk_base::Message msg;
+  while (talk_base::Thread::Current()->Get(&msg)) {
+    if (msg.phandler == NULL) {
+      if (std::find(ids.begin(), ids.end(), msg.message_id) != ids.end())
+        return msg.message_id;
+      std::cout << "orphaned message: " << msg.message_id;
+      continue;
+    }
+    talk_base::Thread::Current()->Dispatch(&msg);
+  }
+  return 0;
+}
+
+#ifdef WIN32
+#pragma warning(disable:4355)
+#endif
+
+class CustomXmppPump : public XmppPumpNotify, public XmppPump {
+public:
+  CustomXmppPump() : XmppPump(this), server_(false) { }
+
+  void Serve(cricket::TunnelSessionClient* client) {
+    client->SignalIncomingTunnel.connect(this,
+      &CustomXmppPump::OnIncomingTunnel);
+    server_ = true;
+  }
+
+  void OnStateChange(buzz::XmppEngine::State state) {
+    switch (state) {
+    case buzz::XmppEngine::STATE_START:
+      std::cout << "connecting..." << std::endl;
+      break;
+    case buzz::XmppEngine::STATE_OPENING:
+      std::cout << "logging in..." << std::endl;
+      break;
+    case buzz::XmppEngine::STATE_OPEN:
+      std::cout << "logged in..." << std::endl;
+      talk_base::Thread::Current()->Post(NULL, MSG_LOGIN_COMPLETE);
+      break;
+    case buzz::XmppEngine::STATE_CLOSED:
+      std::cout << "logged out..." << std::endl;
+      talk_base::Thread::Current()->Post(NULL, MSG_LOGIN_FAILED);
+      break;
+    }
+  }
+
+  void OnIncomingTunnel(cricket::TunnelSessionClient* client, buzz::Jid jid,
+    std::string description, cricket::Session* session) {
+    std::cout << "IncomingTunnel from " << jid.Str()
+      << ": " << description << std::endl;
+    if (!server_ || (file_.get() != NULL)) {
+      client->DeclineTunnel(session);
+      return;
+    }
+    std::string filename;
+    bool send;
+    if (strncmp(description.c_str(), "send:", 5) == 0) {
+      send = true;
+    } else if (strncmp(description.c_str(), "recv:", 5) == 0) {
+      send = false;
+    } else {
+      client->DeclineTunnel(session);
+      return;
+    }
+    filename = description.substr(5);
+    talk_base::StreamInterface* stream = client->AcceptTunnel(session);
+    if (!ProcessStream(stream, filename, send))
+      talk_base::Thread::Current()->Post(NULL, MSG_DONE);
+
+    // TODO: There is a potential memory leak, however, since the PCP
+    // app doesn't work right now, I can't verify the fix actually works, so
+    // comment out the following line until we fix the PCP app.
+
+    // delete stream;
+  }
+
+  bool ProcessStream(talk_base::StreamInterface* stream,
+                     const std::string& filename, bool send) {
+    ASSERT(file_.get() == NULL);
+    sending_ = send;
+    file_.reset(new talk_base::FileStream);
+    buffer_len_ = 0;
+    int err;
+    if (!file_->Open(filename.c_str(), sending_ ? "rb" : "wb", &err)) {
+      std::cerr << "Error opening <" << filename << ">: "
+                << std::strerror(err) << std::endl;
+      return false;
+    }
+    stream->SignalEvent.connect(this, &CustomXmppPump::OnStreamEvent);
+    if (stream->GetState() == talk_base::SS_CLOSED) {
+      std::cerr << "Failed to establish P2P tunnel" << std::endl;
+      return false;
+    }
+    if (stream->GetState() == talk_base::SS_OPEN) {
+      OnStreamEvent(stream,
+        talk_base::SE_OPEN | talk_base::SE_READ | talk_base::SE_WRITE, 0);
+    }
+    return true;
+  }
+
+  void OnStreamEvent(talk_base::StreamInterface* stream, int events,
+                     int error) {
+    if (events & talk_base::SE_CLOSE) {
+      if (error == 0) {
+        std::cout << "Tunnel closed normally" << std::endl;
+      } else {
+        std::cout << "Tunnel closed with error: " << error << std::endl;
+      }
+      Cleanup(stream);
+      return;
+    }
+    if (events & talk_base::SE_OPEN) {
+      std::cout << "Tunnel connected" << std::endl;
+    }
+    talk_base::StreamResult result;
+    size_t count;
+    if (sending_ && (events & talk_base::SE_WRITE)) {
+      LOG(LS_VERBOSE) << "Tunnel SE_WRITE";
+      while (true) {
+        size_t write_pos = 0;
+        while (write_pos < buffer_len_) {
+          result = stream->Write(buffer_ + write_pos, buffer_len_ - write_pos,
+                                &count, &error);
+          if (result == talk_base::SR_SUCCESS) {
+            write_pos += count;
+            continue;
+          }
+          if (result == talk_base::SR_BLOCK) {
+            buffer_len_ -= write_pos;
+            memmove(buffer_, buffer_ + write_pos, buffer_len_);
+            LOG(LS_VERBOSE) << "Tunnel write block";
+            return;
+          }
+          if (result == talk_base::SR_EOS) {
+            std::cout << "Tunnel closed unexpectedly on write" << std::endl;
+          } else {
+            std::cout << "Tunnel write error: " << error << std::endl;
+          }
+          Cleanup(stream);
+          return;
+        }
+        buffer_len_ = 0;
+        while (buffer_len_ < sizeof(buffer_)) {
+          result = file_->Read(buffer_ + buffer_len_,
+                              sizeof(buffer_) - buffer_len_,
+                              &count, &error);
+          if (result == talk_base::SR_SUCCESS) {
+            buffer_len_ += count;
+            continue;
+          }
+          if (result == talk_base::SR_EOS) {
+            if (buffer_len_ > 0)
+              break;
+            std::cout << "End of file" << std::endl;
+            // A hack until we have friendly shutdown
+            Cleanup(stream, true);
+            return;
+          } else if (result == talk_base::SR_BLOCK) {
+            std::cout << "File blocked unexpectedly on read" << std::endl;
+          } else {
+            std::cout << "File read error: " << error << std::endl;
+          }
+          Cleanup(stream);
+          return;
+        }
+      }
+    }
+    if (!sending_ && (events & talk_base::SE_READ)) {
+      LOG(LS_VERBOSE) << "Tunnel SE_READ";
+      while (true) {
+        buffer_len_ = 0;
+        while (buffer_len_ < sizeof(buffer_)) {
+          result = stream->Read(buffer_ + buffer_len_,
+                                sizeof(buffer_) - buffer_len_,
+                                &count, &error);
+          if (result == talk_base::SR_SUCCESS) {
+            buffer_len_ += count;
+            continue;
+          }
+          if (result == talk_base::SR_BLOCK) {
+            if (buffer_len_ > 0)
+              break;
+            LOG(LS_VERBOSE) << "Tunnel read block";
+            return;
+          }
+          if (result == talk_base::SR_EOS) {
+            std::cout << "Tunnel closed unexpectedly on read" << std::endl;
+          } else {
+            std::cout << "Tunnel read error: " << error << std::endl;
+          }
+          Cleanup(stream);
+          return;
+        }
+        size_t write_pos = 0;
+        while (write_pos < buffer_len_) {
+          result = file_->Write(buffer_ + write_pos, buffer_len_ - write_pos,
+                                &count, &error);
+          if (result == talk_base::SR_SUCCESS) {
+            write_pos += count;
+            continue;
+          }
+          if (result == talk_base::SR_EOS) {
+            std::cout << "File closed unexpectedly on write" << std::endl;
+          } else if (result == talk_base::SR_BLOCK) {
+            std::cout << "File blocked unexpectedly on write" << std::endl;
+          } else {
+            std::cout << "File write error: " << error << std::endl;
+          }
+          Cleanup(stream);
+          return;
+        }
+      }
+    }
+  }
+
+  void Cleanup(talk_base::StreamInterface* stream, bool delay = false) {
+    LOG(LS_VERBOSE) << "Closing";
+    stream->Close();
+    file_.reset();
+    if (!server_) {
+      if (delay)
+        talk_base::Thread::Current()->PostDelayed(2000, NULL, MSG_DONE);
+      else
+        talk_base::Thread::Current()->Post(NULL, MSG_DONE);
+    }
+  }
+
+private:
+  bool server_, sending_;
+  talk_base::scoped_ptr<talk_base::FileStream> file_;
+  char buffer_[1024 * 64];
+  size_t buffer_len_;
+};
+
+int main(int argc, char **argv) {
+  talk_base::LogMessage::LogThreads();
+  talk_base::LogMessage::LogTimestamps();
+
+  // TODO: Default the username to the current users's name.
+
+  // Parse the arguments.
+
+  int index = 1;
+  while (index < argc) {
+    std::string name, value;
+    if (!ParseArg(argv[index], &name, &value))
+      break;
+
+    if (name == "help") {
+      Usage();
+    } else if (name == "verbose") {
+      talk_base::LogMessage::LogToDebug(talk_base::LS_VERBOSE);
+    } else if (name == "xmpp-host") {
+      gXmppHost = value;
+    } else if (name == "xmpp-port") {
+      gXmppPort = ParseIntArg(name, value);
+    } else if (name == "xmpp-use-tls") {
+      gXmppUseTls = ParseBoolArg(name, value)?
+          buzz::TLS_REQUIRED : buzz::TLS_DISABLED;
+    } else {
+      Error(std::string("unknown option: ") + name);
+    }
+
+    index += 1;
+  }
+
+  if (index >= argc)
+    Error("bad arguments");
+  gUserJid = buzz::Jid(argv[index++]);
+  if (!gUserJid.IsValid())
+    Error("bad arguments");
+
+  char path[MAX_PATH];
+#if WIN32
+  GetCurrentDirectoryA(MAX_PATH, path);
+#else
+  if (NULL == getcwd(path, MAX_PATH))
+    Error("Unable to get current path");
+#endif
+
+  std::cout << "Directory: " << std::string(path) << std::endl;
+
+  buzz::Jid gSrcJid;
+  buzz::Jid gDstJid;
+  std::string gSrcFile;
+  std::string gDstFile;
+
+  bool as_server = true;
+  if (index + 2 == argc) {
+    ParseFileArg(argv[index], &gSrcJid, &gSrcFile);
+    ParseFileArg(argv[index+1], &gDstJid, &gDstFile);
+    if(gSrcJid.Str().empty() == gDstJid.Str().empty())
+      Error("Exactly one of source JID or destination JID must be empty.");
+    as_server = false;
+  } else if (index != argc) {
+    Error("bad arguments");
+  }
+
+  std::cout << "Password: ";
+  SetConsoleEcho(false);
+  std::cin >> gUserPass.password();
+  SetConsoleEcho(true);
+  std::cout << std::endl;
+
+  talk_base::InitializeSSL();
+  // Log in.
+  CustomXmppPump pump;
+  pump.client()->SignalLogInput.connect(&debug_log_, &DebugLog::Input);
+  pump.client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
+  pump.DoLogin(LoginSettings(), new XmppSocket(gXmppUseTls), 0);
+    //new XmppAuth());
+
+  // Wait until login succeeds.
+  std::vector<uint32> ids;
+  ids.push_back(MSG_LOGIN_COMPLETE);
+  ids.push_back(MSG_LOGIN_FAILED);
+  if (MSG_LOGIN_FAILED == Loop(ids))
+    FatalError("Failed to connect");
+
+  {
+    talk_base::scoped_ptr<buzz::XmlElement> presence(
+      new buzz::XmlElement(buzz::QN_PRESENCE));
+    presence->AddElement(new buzz::XmlElement(buzz::QN_PRIORITY));
+    presence->AddText("-1", 1);
+    pump.SendStanza(presence.get());
+  }
+
+  std::string user_jid_str = pump.client()->jid().Str();
+  std::cout << "Logged in as " << user_jid_str << std::endl;
+
+  // Prepare the random number generator.
+  talk_base::InitRandom(user_jid_str.c_str(), user_jid_str.size());
+
+  // Create the P2P session manager.
+  talk_base::BasicNetworkManager network_manager;
+  AutoPortAllocator allocator(&network_manager, "pcp_agent");
+  allocator.SetXmppClient(pump.client());
+  cricket::SessionManager session_manager(&allocator);
+#ifdef USE_SSL_TUNNEL
+  cricket::SecureTunnelSessionClient session_client(pump.client()->jid(),
+                                                    &session_manager);
+  if (!session_client.GenerateIdentity())
+    FatalError("Failed to generate SSL identity");
+#else  // !USE_SSL_TUNNEL
+  cricket::TunnelSessionClient session_client(pump.client()->jid(),
+                                              &session_manager);
+#endif  // USE_SSL_TUNNEL
+  cricket::SessionManagerTask *receiver =
+      new cricket::SessionManagerTask(pump.client(), &session_manager);
+  receiver->EnableOutgoingMessages();
+  receiver->Start();
+
+  bool success = true;
+
+  // Establish the appropriate connection.
+  if (as_server) {
+    pump.Serve(&session_client);
+  } else {
+    talk_base::StreamInterface* stream = NULL;
+    std::string filename;
+    bool sending;
+    if (gSrcJid.Str().empty()) {
+      std::string message("recv:");
+      message.append(gDstFile);
+      stream = session_client.CreateTunnel(gDstJid, message);
+      filename = gSrcFile;
+      sending = true;
+    } else {
+      std::string message("send:");
+      message.append(gSrcFile);
+      stream = session_client.CreateTunnel(gSrcJid, message);
+      filename = gDstFile;
+      sending = false;
+    }
+    success = pump.ProcessStream(stream, filename, sending);
+  }
+
+  if (success) {
+    // Wait until the copy is done.
+    ids.clear();
+    ids.push_back(MSG_DONE);
+    ids.push_back(MSG_LOGIN_FAILED);
+    Loop(ids);
+  }
+
+  // Log out.
+  pump.DoDisconnect();
+
+  return 0;
+}
diff --git a/talk/examples/peerconnection/client/conductor.cc b/talk/examples/peerconnection/client/conductor.cc
new file mode 100644
index 0000000..3557f11
--- /dev/null
+++ b/talk/examples/peerconnection/client/conductor.cc
@@ -0,0 +1,399 @@
+/*
+ * 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/examples/peerconnection/client/conductor.h"
+
+#include <utility>
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_capture/main/interface/video_capture_factory.h"
+#else
+#include "third_party/webrtc/files/include/video_capture_factory.h"
+#endif
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/examples/peerconnection/client/defaults.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/phone/videorendererfactory.h"
+
+Conductor::Conductor(PeerConnectionClient* client, MainWindow* main_wnd)
+  : peer_id_(-1),
+    client_(client),
+    main_wnd_(main_wnd) {
+  client_->RegisterObserver(this);
+  main_wnd->RegisterObserver(this);
+}
+
+Conductor::~Conductor() {
+  ASSERT(peer_connection_.get() == NULL);
+}
+
+bool Conductor::connection_active() const {
+  return peer_connection_.get() != NULL;
+}
+
+void Conductor::Close() {
+  client_->SignOut();
+  DeletePeerConnection();
+}
+
+bool Conductor::InitializePeerConnection() {
+  ASSERT(peer_connection_factory_.get() == NULL);
+  ASSERT(peer_connection_.get() == NULL);
+
+  peer_connection_factory_  = webrtc::CreatePeerConnectionFactory();
+
+  if (!peer_connection_factory_.get()) {
+    main_wnd_->MessageBox("Error",
+        "Failed to initialize PeerConnectionFactory", true);
+    DeletePeerConnection();
+    return false;
+  }
+
+  peer_connection_ = peer_connection_factory_->CreatePeerConnection(
+      GetPeerConnectionString(), this);
+
+  if (!peer_connection_.get()) {
+    main_wnd_->MessageBox("Error",
+        "CreatePeerConnection failed", true);
+    DeletePeerConnection();
+  }
+  return peer_connection_.get() != NULL;
+}
+
+void Conductor::DeletePeerConnection() {
+  peer_connection_ = NULL;
+  active_streams_.clear();
+  peer_connection_factory_ = NULL;
+  peer_id_ = -1;
+}
+
+void Conductor::EnsureStreamingUI() {
+  ASSERT(peer_connection_.get() != NULL);
+  if (main_wnd_->IsWindow()) {
+    if (main_wnd_->current_ui() != MainWindow::STREAMING)
+      main_wnd_->SwitchToStreamingUI();
+  }
+}
+
+//
+// PeerConnectionObserver implementation.
+//
+
+void Conductor::OnError() {
+  LOG(LS_ERROR) << __FUNCTION__;
+  main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_ERROR, NULL);
+}
+
+void Conductor::OnSignalingMessage(const std::string& msg) {
+  LOG(INFO) << __FUNCTION__;
+
+  std::string* msg_copy = new std::string(msg);
+  main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg_copy);
+}
+
+// Called when a remote stream is added
+void Conductor::OnAddStream(webrtc::MediaStreamInterface* stream) {
+  LOG(INFO) << __FUNCTION__ << " " << stream->label();
+
+  stream->AddRef();
+  main_wnd_->QueueUIThreadCallback(NEW_STREAM_ADDED,
+                                   stream);
+}
+
+void Conductor::OnRemoveStream(webrtc::MediaStreamInterface* stream) {
+  LOG(INFO) << __FUNCTION__ << " " << stream->label();
+  stream->AddRef();
+  main_wnd_->QueueUIThreadCallback(STREAM_REMOVED,
+                                   stream);
+}
+
+//
+// PeerConnectionClientObserver implementation.
+//
+
+void Conductor::OnSignedIn() {
+  LOG(INFO) << __FUNCTION__;
+  main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::OnDisconnected() {
+  LOG(INFO) << __FUNCTION__;
+
+  DeletePeerConnection();
+
+  if (main_wnd_->IsWindow())
+    main_wnd_->SwitchToConnectUI();
+}
+
+void Conductor::OnPeerConnected(int id, const std::string& name) {
+  LOG(INFO) << __FUNCTION__;
+  // Refresh the list if we're showing it.
+  if (main_wnd_->current_ui() == MainWindow::LIST_PEERS)
+    main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::OnPeerDisconnected(int id) {
+  LOG(INFO) << __FUNCTION__;
+  if (id == peer_id_) {
+    LOG(INFO) << "Our peer disconnected";
+    main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL);
+  } else {
+    // Refresh the list if we're showing it.
+    if (main_wnd_->current_ui() == MainWindow::LIST_PEERS)
+      main_wnd_->SwitchToPeerList(client_->peers());
+  }
+}
+
+void Conductor::OnMessageFromPeer(int peer_id, const std::string& message) {
+  ASSERT(peer_id_ == peer_id || peer_id_ == -1);
+  ASSERT(!message.empty());
+
+  if (!peer_connection_.get()) {
+    ASSERT(peer_id_ == -1);
+    peer_id_ = peer_id;
+
+    // Got an offer.  Give it to the PeerConnection instance.
+    // Once processed, we will get a callback to OnSignalingMessage with
+    // our 'answer' which we'll send to the peer.
+    LOG(INFO) << "Got an offer from our peer: " << peer_id;
+    if (!InitializePeerConnection()) {
+      LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance";
+      client_->SignOut();
+      return;
+    }
+  } else if (peer_id != peer_id_) {
+    ASSERT(peer_id_ != -1);
+    LOG(WARNING) << "Received an offer from a peer while already in a "
+                    "conversation with a different peer.";
+    return;
+  }
+
+  peer_connection_->ProcessSignalingMessage(message);
+}
+
+void Conductor::OnMessageSent(int err) {
+  // Process the next pending message if any.
+  main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, NULL);
+}
+
+//
+// MainWndCallback implementation.
+//
+
+bool Conductor::StartLogin(const std::string& server, int port) {
+  if (client_->is_connected())
+    return false;
+
+  if (!client_->Connect(server, port, GetPeerName())) {
+    main_wnd_->MessageBox("Error", ("Failed to connect to " + server).c_str(),
+                          true);
+    return false;
+  }
+
+  return true;
+}
+
+void Conductor::DisconnectFromServer() {
+  if (client_->is_connected())
+    client_->SignOut();
+}
+
+void Conductor::ConnectToPeer(int peer_id) {
+  ASSERT(peer_id_ == -1);
+  ASSERT(peer_id != -1);
+
+  if (peer_connection_.get()) {
+    main_wnd_->MessageBox("Error",
+        "We only support connecting to one peer at a time", true);
+    return;
+  }
+
+  if (InitializePeerConnection()) {
+    peer_id_ = peer_id;
+    AddStreams();
+  } else {
+    main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true);
+  }
+}
+
+talk_base::scoped_refptr<webrtc::VideoCaptureModule>
+Conductor::OpenVideoCaptureDevice() {
+  webrtc::VideoCaptureModule::DeviceInfo* device_info(
+      webrtc::VideoCaptureFactory::CreateDeviceInfo(0));
+  talk_base::scoped_refptr<webrtc::VideoCaptureModule> video_device;
+
+  const size_t kMaxDeviceNameLength = 128;
+  const size_t kMaxUniqueIdLength = 256;
+  uint8 device_name[kMaxDeviceNameLength];
+  uint8 unique_id[kMaxUniqueIdLength];
+
+  const size_t device_count = device_info->NumberOfDevices();
+  for (size_t i = 0; i < device_count; ++i) {
+    // Get the name of the video capture device.
+    device_info->GetDeviceName(i, device_name, kMaxDeviceNameLength, unique_id,
+        kMaxUniqueIdLength);
+    // Try to open this device.
+    video_device =
+        webrtc::VideoCaptureFactory::Create(0, unique_id);
+    if (video_device.get())
+      break;
+  }
+  delete device_info;
+  return video_device;
+}
+
+void Conductor::AddStreams() {
+  if (active_streams_.find(kStreamLabel) != active_streams_.end())
+    return;  // Already added.
+
+  talk_base::scoped_refptr<webrtc::LocalAudioTrackInterface> audio_track(
+      peer_connection_factory_->CreateLocalAudioTrack(kAudioLabel, NULL));
+
+  talk_base::scoped_refptr<webrtc::LocalVideoTrackInterface> video_track(
+      peer_connection_factory_->CreateLocalVideoTrack(
+          kVideoLabel, CreateVideoCapturer(OpenVideoCaptureDevice())));
+
+  video_track->SetRenderer(main_wnd_->local_renderer());
+
+  talk_base::scoped_refptr<webrtc::LocalMediaStreamInterface> stream =
+      peer_connection_factory_->CreateLocalMediaStream(kStreamLabel);
+
+  stream->AddTrack(audio_track);
+  stream->AddTrack(video_track);
+  peer_connection_->AddStream(stream);
+  peer_connection_->CommitStreamChanges();
+  typedef std::pair<std::string,
+                    talk_base::scoped_refptr<webrtc::MediaStreamInterface> >
+      MediaStreamPair;
+  active_streams_.insert(MediaStreamPair(stream->label(), stream));
+  main_wnd_->SwitchToStreamingUI();
+}
+
+void Conductor::DisconnectFromCurrentPeer() {
+  LOG(INFO) << __FUNCTION__;
+  if (peer_connection_.get()) {
+    client_->SendHangUp(peer_id_);
+    DeletePeerConnection();
+  }
+
+  if (main_wnd_->IsWindow())
+    main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::UIThreadCallback(int msg_id, void* data) {
+  switch (msg_id) {
+    case PEER_CONNECTION_CLOSED:
+      LOG(INFO) << "PEER_CONNECTION_CLOSED";
+      DeletePeerConnection();
+
+      ASSERT(active_streams_.empty());
+
+      if (main_wnd_->IsWindow()) {
+        if (client_->is_connected()) {
+          main_wnd_->SwitchToPeerList(client_->peers());
+        } else {
+          main_wnd_->SwitchToConnectUI();
+        }
+      } else {
+        DisconnectFromServer();
+      }
+      break;
+
+    case SEND_MESSAGE_TO_PEER: {
+      LOG(INFO) << "SEND_MESSAGE_TO_PEER";
+      std::string* msg = reinterpret_cast<std::string*>(data);
+      if (msg) {
+        // For convenience, we always run the message through the queue.
+        // This way we can be sure that messages are sent to the server
+        // in the same order they were signaled without much hassle.
+        pending_messages_.push_back(msg);
+      }
+
+      if (!pending_messages_.empty() && !client_->IsSendingMessage()) {
+        msg = pending_messages_.front();
+        pending_messages_.pop_front();
+
+        if (!client_->SendToPeer(peer_id_, *msg) && peer_id_ != -1) {
+          LOG(LS_ERROR) << "SendToPeer failed";
+          DisconnectFromServer();
+        }
+        delete msg;
+      }
+
+      if (!peer_connection_.get())
+        peer_id_ = -1;
+
+      break;
+    }
+
+    case PEER_CONNECTION_ADDSTREAMS:
+      AddStreams();
+      break;
+
+    case PEER_CONNECTION_ERROR:
+      main_wnd_->MessageBox("Error", "an unknown error occurred", true);
+      break;
+
+    case NEW_STREAM_ADDED: {
+      webrtc::MediaStreamInterface* stream =
+          reinterpret_cast<webrtc::MediaStreamInterface*>(
+          data);
+      talk_base::scoped_refptr<webrtc::VideoTracks> tracks =
+          stream->video_tracks();
+      for (size_t i = 0; i < tracks->count(); ++i) {
+        webrtc::VideoTrackInterface* track = tracks->at(i);
+        LOG(INFO) << "Setting video renderer for track: " << track->label();
+        track->SetRenderer(main_wnd_->remote_renderer());
+      }
+      // If we haven't shared any streams with this peer (we're the receiver)
+      // then do so now.
+      if (active_streams_.empty())
+        AddStreams();
+      stream->Release();
+      break;
+    }
+
+    case STREAM_REMOVED: {
+      webrtc::MediaStreamInterface* stream =
+          reinterpret_cast<webrtc::MediaStreamInterface*>(
+          data);
+      active_streams_.erase(stream->label());
+      stream->Release();
+      if (active_streams_.empty()) {
+        LOG(INFO) << "All streams have been closed.";
+        main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL);
+      }
+      break;
+    }
+
+    default:
+      ASSERT(false);
+      break;
+  }
+}
diff --git a/talk/examples/peerconnection/client/conductor.h b/talk/examples/peerconnection/client/conductor.h
new file mode 100644
index 0000000..4268d74
--- /dev/null
+++ b/talk/examples/peerconnection/client/conductor.h
@@ -0,0 +1,134 @@
+/*
+ * 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 PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_
+#pragma once
+
+#include <deque>
+#include <map>
+#include <set>
+#include <string>
+
+#include "talk/examples/peerconnection/client/main_wnd.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace webrtc {
+class VideoCaptureModule;
+}  // namespace webrtc
+
+namespace cricket {
+class VideoRenderer;
+}  // namespace cricket
+
+class Conductor
+  : public webrtc::PeerConnectionObserver,
+    public PeerConnectionClientObserver,
+    public MainWndCallback {
+ public:
+  enum CallbackID {
+    MEDIA_CHANNELS_INITIALIZED = 1,
+    PEER_CONNECTION_CLOSED,
+    SEND_MESSAGE_TO_PEER,
+    PEER_CONNECTION_ADDSTREAMS,
+    PEER_CONNECTION_ERROR,
+    NEW_STREAM_ADDED,
+    STREAM_REMOVED,
+  };
+
+  Conductor(PeerConnectionClient* client, MainWindow* main_wnd);
+  ~Conductor();
+
+  bool connection_active() const;
+
+  virtual void Close();
+
+ protected:
+  bool InitializePeerConnection();
+  void DeletePeerConnection();
+  void EnsureStreamingUI();
+  void AddStreams();
+  talk_base::scoped_refptr<webrtc::VideoCaptureModule> OpenVideoCaptureDevice();
+
+  //
+  // PeerConnectionObserver implementation.
+  //
+  virtual void OnError();
+  virtual void OnMessage(const std::string& msg) {}
+  virtual void OnSignalingMessage(const std::string& msg);
+  virtual void OnStateChange(
+      webrtc::PeerConnectionObserver::StateType state_changed) {}
+  virtual void OnAddStream(webrtc::MediaStreamInterface* stream);
+  virtual void OnRemoveStream(webrtc::MediaStreamInterface* stream);
+
+
+  //
+  // PeerConnectionClientObserver implementation.
+  //
+
+  virtual void OnSignedIn();
+
+  virtual void OnDisconnected();
+
+  virtual void OnPeerConnected(int id, const std::string& name);
+
+  virtual void OnPeerDisconnected(int id);
+
+  virtual void OnMessageFromPeer(int peer_id, const std::string& message);
+
+  virtual void OnMessageSent(int err);
+
+  //
+  // MainWndCallback implementation.
+  //
+
+  virtual bool StartLogin(const std::string& server, int port);
+
+  virtual void DisconnectFromServer();
+
+  virtual void ConnectToPeer(int peer_id);
+
+  virtual void DisconnectFromCurrentPeer();
+
+  virtual void UIThreadCallback(int msg_id, void* data);
+
+ protected:
+  int peer_id_;
+  talk_base::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
+  talk_base::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
+      peer_connection_factory_;
+  PeerConnectionClient* client_;
+  MainWindow* main_wnd_;
+  std::deque<std::string*> pending_messages_;
+  std::map<std::string, talk_base::scoped_refptr<webrtc::MediaStreamInterface> >
+      active_streams_;
+};
+
+#endif  // PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_
diff --git a/talk/examples/peerconnection/client/defaults.cc b/talk/examples/peerconnection/client/defaults.cc
new file mode 100644
index 0000000..eb895d0
--- /dev/null
+++ b/talk/examples/peerconnection/client/defaults.cc
@@ -0,0 +1,75 @@
+/*
+ * 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/examples/peerconnection/client/defaults.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <unistd.h>
+#endif
+
+#include "talk/base/common.h"
+
+const char kAudioLabel[] = "audio_label";
+const char kVideoLabel[] = "video_label";
+const char kStreamLabel[] = "stream_label";
+const uint16 kDefaultServerPort = 8888;
+
+std::string GetEnvVarOrDefault(const char* env_var_name,
+                               const char* default_value) {
+  std::string value;
+  const char* env_var = getenv(env_var_name);
+  if (env_var)
+    value = env_var;
+
+  if (value.empty())
+    value = default_value;
+
+  return value;
+}
+
+std::string GetPeerConnectionString() {
+  return GetEnvVarOrDefault("WEBRTC_CONNECT", "STUN stun.l.google.com:19302");
+}
+
+std::string GetDefaultServerName() {
+  return GetEnvVarOrDefault("WEBRTC_SERVER", "localhost");
+}
+
+std::string GetPeerName() {
+  char computer_name[256];
+  if (gethostname(computer_name, ARRAY_SIZE(computer_name)) != 0)
+    strcpy(computer_name, "host");
+  std::string ret(GetEnvVarOrDefault("USERNAME", "user"));
+  ret += '@';
+  ret += computer_name;
+  return ret;
+}
diff --git a/talk/examples/peerconnection/client/defaults.h b/talk/examples/peerconnection/client/defaults.h
new file mode 100644
index 0000000..f646149
--- /dev/null
+++ b/talk/examples/peerconnection/client/defaults.h
@@ -0,0 +1,47 @@
+/*
+ * 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 PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_
+#pragma once
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+extern const char kAudioLabel[];
+extern const char kVideoLabel[];
+extern const char kStreamLabel[];
+extern const uint16 kDefaultServerPort;
+
+std::string GetEnvVarOrDefault(const char* env_var_name,
+                               const char* default_value);
+std::string GetPeerConnectionString();
+std::string GetDefaultServerName();
+std::string GetPeerName();
+
+#endif  // PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_
diff --git a/talk/examples/peerconnection/client/linux/main.cc b/talk/examples/peerconnection/client/linux/main.cc
new file mode 100644
index 0000000..ab912ab
--- /dev/null
+++ b/talk/examples/peerconnection/client/linux/main.cc
@@ -0,0 +1,102 @@
+/*
+ * 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 <gtk/gtk.h>
+
+#include "talk/examples/peerconnection/client/conductor.h"
+#include "talk/examples/peerconnection/client/linux/main_wnd.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+
+#include "talk/base/thread.h"
+
+class CustomSocketServer : public talk_base::PhysicalSocketServer {
+ public:
+  CustomSocketServer(talk_base::Thread* thread, GtkMainWnd* wnd)
+      : thread_(thread), wnd_(wnd), conductor_(NULL), client_(NULL) {}
+  virtual ~CustomSocketServer() {}
+  
+  void set_client(PeerConnectionClient* client) { client_ = client; }
+  void set_conductor(Conductor* conductor) { conductor_ = conductor; }
+
+  // Override so that we can also pump the GTK message loop.
+  virtual bool Wait(int cms, bool process_io) {
+    // Pump GTK events.
+    // TODO: We really should move either the socket server or UI to a
+    // different thread.  Alternatively we could look at merging the two loops
+    // by implementing a dispatcher for the socket server and/or use
+    // g_main_context_set_poll_func.
+      while (gtk_events_pending())
+        gtk_main_iteration();
+    
+    if (!wnd_->IsWindow() && !conductor_->connection_active() &&
+        client_ != NULL && !client_->is_connected()) {
+      thread_->Quit();
+    }
+    return talk_base::PhysicalSocketServer::Wait(0/*cms == -1 ? 1 : cms*/,
+                                                 process_io);
+  }
+
+ protected:
+  talk_base::Thread* thread_;
+  GtkMainWnd* wnd_;
+  Conductor* conductor_;
+  PeerConnectionClient* client_;
+};
+
+int main(int argc, char* argv[]) {
+  gtk_init(&argc, &argv);
+  g_type_init();
+  g_thread_init(NULL);
+
+  GtkMainWnd wnd;
+  wnd.Create();
+
+  talk_base::AutoThread auto_thread;
+  talk_base::Thread* thread = talk_base::Thread::Current();
+  CustomSocketServer socket_server(thread, &wnd);
+  thread->set_socketserver(&socket_server);
+
+  // Must be constructed after we set the socketserver.
+  PeerConnectionClient client;
+  Conductor conductor(&client, &wnd);
+  socket_server.set_client(&client);
+  socket_server.set_conductor(&conductor);
+
+  thread->Run();
+
+  // gtk_main();
+  wnd.Destroy();
+
+  thread->set_socketserver(NULL);
+  // TODO: Run the Gtk main loop to tear down the connection.
+  //while (gtk_events_pending()) {
+  //  gtk_main_iteration();
+  //}
+
+  return 0;
+}
+
diff --git a/talk/examples/peerconnection/client/linux/main_wnd.cc b/talk/examples/peerconnection/client/linux/main_wnd.cc
new file mode 100644
index 0000000..3335985
--- /dev/null
+++ b/talk/examples/peerconnection/client/linux/main_wnd.cc
@@ -0,0 +1,482 @@
+/*
+ * 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/examples/peerconnection/client/linux/main_wnd.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <stddef.h>
+
+#include "talk/examples/peerconnection/client/defaults.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+
+using talk_base::sprintfn;
+
+namespace {
+
+//
+// Simple static functions that simply forward the callback to the
+// GtkMainWnd instance.
+//
+
+gboolean OnDestroyedCallback(GtkWidget* widget, GdkEvent* event,
+                             gpointer data) {
+  reinterpret_cast<GtkMainWnd*>(data)->OnDestroyed(widget, event);
+  return FALSE;
+}
+
+void OnClickedCallback(GtkWidget* widget, gpointer data) {
+  reinterpret_cast<GtkMainWnd*>(data)->OnClicked(widget);
+}
+
+gboolean OnKeyPressCallback(GtkWidget* widget, GdkEventKey* key,
+                            gpointer data) {
+  reinterpret_cast<GtkMainWnd*>(data)->OnKeyPress(widget, key);
+  return false;
+}
+
+void OnRowActivatedCallback(GtkTreeView* tree_view, GtkTreePath* path,
+                            GtkTreeViewColumn* column, gpointer data) {
+  reinterpret_cast<GtkMainWnd*>(data)->OnRowActivated(tree_view, path, column);
+}
+
+// Creates a tree view, that we use to display the list of peers.
+void InitializeList(GtkWidget* list) {
+  GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
+  GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes(
+      "List Items", renderer, "text", 0, NULL);
+  gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+  GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
+  gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
+  g_object_unref(store);
+}
+
+// Adds an entry to a tree view.
+void AddToList(GtkWidget* list, const gchar* str, int value) {
+  GtkListStore* store = GTK_LIST_STORE(
+      gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
+
+  GtkTreeIter iter;
+  gtk_list_store_append(store, &iter);
+  gtk_list_store_set(store, &iter, 0, str, 1, value, -1);
+}
+
+struct UIThreadCallbackData {
+  explicit UIThreadCallbackData(MainWndCallback* cb, int id, void* d)
+      : callback(cb), msg_id(id), data(d) {}
+  MainWndCallback* callback;
+  int msg_id;
+  void* data;
+};
+
+gboolean HandleUIThreadCallback(gpointer data) {
+  UIThreadCallbackData* cb_data = reinterpret_cast<UIThreadCallbackData*>(data);
+  cb_data->callback->UIThreadCallback(cb_data->msg_id, cb_data->data);
+  delete cb_data;
+  return false;
+}
+
+gboolean Redraw(gpointer data) {
+  GtkMainWnd* wnd = reinterpret_cast<GtkMainWnd*>(data);
+  wnd->OnRedraw();
+  return false;
+}
+}  // end anonymous
+
+//
+// GtkMainWnd implementation.
+//
+
+GtkMainWnd::GtkMainWnd()
+    : window_(NULL), draw_area_(NULL), vbox_(NULL), server_edit_(NULL),
+      port_edit_(NULL), peer_list_(NULL), callback_(NULL),
+      server_("localhost") {
+  char buffer[10];
+  sprintfn(buffer, sizeof(buffer), "%i", kDefaultServerPort);
+  port_ = buffer;
+}
+
+GtkMainWnd::~GtkMainWnd() {
+  ASSERT(!IsWindow());
+}
+
+void GtkMainWnd::RegisterObserver(MainWndCallback* callback) {
+  callback_ = callback;
+}
+
+bool GtkMainWnd::IsWindow() {
+  return window_ != NULL && GTK_IS_WINDOW(window_);
+}
+
+void GtkMainWnd::MessageBox(const char* caption, const char* text,
+                            bool is_error) {
+  GtkWidget* dialog = gtk_message_dialog_new(GTK_WINDOW(window_),
+      GTK_DIALOG_DESTROY_WITH_PARENT,
+      is_error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO,
+      GTK_BUTTONS_CLOSE, "%s", text);
+  gtk_window_set_title(GTK_WINDOW(dialog), caption);
+  gtk_dialog_run(GTK_DIALOG(dialog));
+  gtk_widget_destroy(dialog);
+}
+
+MainWindow::UI GtkMainWnd::current_ui() {
+  if (vbox_)
+    return CONNECT_TO_SERVER;
+
+  if (peer_list_)
+    return LIST_PEERS;
+
+  return STREAMING;
+}
+
+webrtc::VideoRendererWrapperInterface* GtkMainWnd::local_renderer() {
+  if (!local_renderer_wrapper_.get())
+    local_renderer_wrapper_  =
+        webrtc::CreateVideoRenderer(new VideoRenderer(this));
+  return local_renderer_wrapper_.get();
+}
+
+webrtc::VideoRendererWrapperInterface*  GtkMainWnd::remote_renderer() {
+  if (!remote_renderer_wrapper_.get())
+    remote_renderer_wrapper_ =
+        webrtc::CreateVideoRenderer(new VideoRenderer(this));
+  return remote_renderer_wrapper_.get();
+}
+
+void GtkMainWnd::QueueUIThreadCallback(int msg_id, void* data) {
+  g_idle_add(HandleUIThreadCallback,
+             new UIThreadCallbackData(callback_, msg_id, data));
+}
+
+bool GtkMainWnd::Create() {
+  ASSERT(window_ == NULL);
+
+  window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  if (window_) {
+    gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER);
+    gtk_window_set_default_size(GTK_WINDOW(window_), 640, 480);
+    gtk_window_set_title(GTK_WINDOW(window_), "PeerConnection client");
+    g_signal_connect(G_OBJECT(window_), "delete-event",
+                     G_CALLBACK(&OnDestroyedCallback), this);
+    g_signal_connect(window_, "key-press-event", G_CALLBACK(OnKeyPressCallback),
+                     this);
+
+    SwitchToConnectUI();
+  }
+
+  return window_ != NULL;
+}
+
+bool GtkMainWnd::Destroy() {
+  if (!IsWindow())
+    return false;
+
+  gtk_widget_destroy(window_);
+  window_ = NULL;
+
+  return true;
+}
+
+void GtkMainWnd::SwitchToConnectUI() {
+  LOG(INFO) << __FUNCTION__;
+
+  ASSERT(IsWindow());
+  ASSERT(vbox_ == NULL);
+
+  gtk_container_set_border_width(GTK_CONTAINER(window_), 10);
+
+  if (peer_list_) {
+    gtk_widget_destroy(peer_list_);
+    peer_list_ = NULL;
+  }
+
+  vbox_ = gtk_vbox_new(FALSE, 5);
+  GtkWidget* valign = gtk_alignment_new(0, 1, 0, 0);
+  gtk_container_add(GTK_CONTAINER(vbox_), valign);
+  gtk_container_add(GTK_CONTAINER(window_), vbox_);
+
+  GtkWidget* hbox = gtk_hbox_new(FALSE, 5);
+
+  GtkWidget* label = gtk_label_new("Server");
+  gtk_container_add(GTK_CONTAINER(hbox), label);
+
+  server_edit_ = gtk_entry_new();
+  gtk_entry_set_text(GTK_ENTRY(server_edit_), server_.c_str());
+  gtk_widget_set_size_request(server_edit_, 400, 30);
+  gtk_container_add(GTK_CONTAINER(hbox), server_edit_);
+
+  port_edit_ = gtk_entry_new();
+  gtk_entry_set_text(GTK_ENTRY(port_edit_), port_.c_str());
+  gtk_widget_set_size_request(port_edit_, 70, 30);
+  gtk_container_add(GTK_CONTAINER(hbox), port_edit_);
+
+  GtkWidget* button = gtk_button_new_with_label("Connect");
+  gtk_widget_set_size_request(button, 70, 30);
+  g_signal_connect(button, "clicked", G_CALLBACK(OnClickedCallback), this);
+  gtk_container_add(GTK_CONTAINER(hbox), button);
+
+  GtkWidget* halign = gtk_alignment_new(1, 0, 0, 0);
+  gtk_container_add(GTK_CONTAINER(halign), hbox);
+  gtk_box_pack_start(GTK_BOX(vbox_), halign, FALSE, FALSE, 0);
+
+  gtk_widget_show_all(window_);
+}
+
+void GtkMainWnd::SwitchToPeerList(const Peers& peers) {
+  LOG(INFO) << __FUNCTION__;
+
+  // Clean up buffers from a potential previous session.
+  local_renderer_wrapper_ = NULL;
+  remote_renderer_wrapper_ = NULL;
+
+  if (!peer_list_) {
+    gtk_container_set_border_width(GTK_CONTAINER(window_), 0);
+    if (vbox_) {
+      gtk_widget_destroy(vbox_);
+      vbox_ = NULL;
+      server_edit_ = NULL;
+      port_edit_ = NULL;
+    } else if (draw_area_) {
+      gtk_widget_destroy(draw_area_);
+      draw_area_ = NULL;
+      draw_buffer_.reset();
+    }
+
+    peer_list_ = gtk_tree_view_new();
+    g_signal_connect(peer_list_, "row-activated",
+                     G_CALLBACK(OnRowActivatedCallback), this);
+    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(peer_list_), FALSE);
+    InitializeList(peer_list_);
+    gtk_container_add(GTK_CONTAINER(window_), peer_list_);
+    gtk_widget_show_all(window_);
+  } else {
+    GtkListStore* store =
+        GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(peer_list_)));
+    gtk_list_store_clear(store);
+  }
+
+  AddToList(peer_list_, "List of currently connected peers:", -1);
+  for (Peers::const_iterator i = peers.begin(); i != peers.end(); ++i)
+    AddToList(peer_list_, i->second.c_str(), i->first);
+}
+
+void GtkMainWnd::SwitchToStreamingUI() {
+  LOG(INFO) << __FUNCTION__;
+
+  ASSERT(draw_area_ == NULL);
+
+  gtk_container_set_border_width(GTK_CONTAINER(window_), 0);
+  if (peer_list_) {
+    gtk_widget_destroy(peer_list_);
+    peer_list_ = NULL;
+  }
+
+  draw_area_ = gtk_drawing_area_new();
+  gtk_container_add(GTK_CONTAINER(window_), draw_area_);
+
+  gtk_widget_show_all(window_);
+}
+
+void GtkMainWnd::OnDestroyed(GtkWidget* widget, GdkEvent* event) {
+  callback_->Close();
+  window_ = NULL;
+  draw_area_ = NULL;
+  vbox_ = NULL;
+  server_edit_ = NULL;
+  port_edit_ = NULL;
+  peer_list_ = NULL;
+}
+
+void GtkMainWnd::OnClicked(GtkWidget* widget) {
+  server_ = gtk_entry_get_text(GTK_ENTRY(server_edit_));
+  port_ = gtk_entry_get_text(GTK_ENTRY(port_edit_));
+  int port = port_.length() ? atoi(port_.c_str()) : 0;
+  callback_->StartLogin(server_, port);
+}
+
+void GtkMainWnd::OnKeyPress(GtkWidget* widget, GdkEventKey* key) {
+  if (key->type == GDK_KEY_PRESS) {
+    switch (key->keyval) {
+     case GDK_Escape:
+       if (draw_area_) {
+         callback_->DisconnectFromCurrentPeer();
+       } else if (peer_list_) {
+         callback_->DisconnectFromServer();
+       }
+       break;
+
+     case GDK_KP_Enter:
+     case GDK_Return:
+       if (vbox_) {
+         OnClicked(NULL);
+       } else if (peer_list_) {
+         // OnRowActivated will be called automatically when the user
+         // presses enter.
+       }
+       break;
+
+     default:
+       break;
+    }
+  }
+}
+
+void GtkMainWnd::OnRowActivated(GtkTreeView* tree_view, GtkTreePath* path,
+                                GtkTreeViewColumn* column) {
+  ASSERT(peer_list_ != NULL);
+  GtkTreeIter iter;
+  GtkTreeModel* model;
+  GtkTreeSelection* selection =
+      gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+  if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+     char* text;
+     int id = -1;
+     gtk_tree_model_get(model, &iter, 0, &text, 1, &id,  -1);
+     if (id != -1)
+       callback_->ConnectToPeer(id);
+     g_free(text);
+  }
+}
+
+void GtkMainWnd::OnRedraw() {
+  gdk_threads_enter();
+
+  VideoRenderer* remote_renderer =
+      static_cast<VideoRenderer*>(remote_renderer_wrapper_->renderer());
+  if (remote_renderer && remote_renderer->image() != NULL &&
+      draw_area_ != NULL) {
+    int width = remote_renderer->width();
+    int height = remote_renderer->height();
+
+    if (!draw_buffer_.get()) {
+      draw_buffer_size_ = (width * height * 4) * 4;
+      draw_buffer_.reset(new uint8[draw_buffer_size_]);
+      gtk_widget_set_size_request(draw_area_, width * 2, height * 2);
+    }
+
+    const uint32* image = reinterpret_cast<const uint32*>(
+        remote_renderer->image());
+    uint32* scaled = reinterpret_cast<uint32*>(draw_buffer_.get());
+    for (int r = 0; r < height; ++r) {
+      for (int c = 0; c < width; ++c) {
+        int x = c * 2;
+        scaled[x] = scaled[x + 1] = image[c];
+      }
+
+      uint32* prev_line = scaled;
+      scaled += width * 2;
+      memcpy(scaled, prev_line, (width * 2) * 4);
+
+      image += width;
+      scaled += width * 2;
+    }
+
+    VideoRenderer* local_renderer =
+        static_cast<VideoRenderer*>(local_renderer_wrapper_->renderer());
+    if (local_renderer && local_renderer->image()) {
+      image = reinterpret_cast<const uint32*>(local_renderer->image());
+      scaled = reinterpret_cast<uint32*>(draw_buffer_.get());
+      // Position the local preview on the right side.
+      scaled += (width * 2) - (local_renderer->width() / 2);
+      // right margin...
+      scaled -= 10;
+      // ... towards the bottom.
+      scaled += (height * width * 4) -
+                ((local_renderer->height() / 2) *
+                 (local_renderer->width() / 2) * 4);
+      // bottom margin...
+      scaled -= (width * 2) * 5;
+      for (int r = 0; r < local_renderer->height(); r += 2) {
+        for (int c = 0; c < local_renderer->width(); c += 2) {
+          scaled[c / 2] = image[c + r * local_renderer->width()];
+        }
+        scaled += width * 2;
+      }
+    }
+
+    gdk_draw_rgb_32_image(draw_area_->window,
+                          draw_area_->style->fg_gc[GTK_STATE_NORMAL],
+                          0,
+                          0,
+                          width * 2,
+                          height * 2,
+                          GDK_RGB_DITHER_MAX,
+                          draw_buffer_.get(),
+                          (width * 2) * 4);
+  }
+
+  gdk_threads_leave();
+}
+
+GtkMainWnd::VideoRenderer::VideoRenderer(GtkMainWnd* main_wnd)
+    : width_(0), height_(0), main_wnd_(main_wnd) {
+}
+
+GtkMainWnd::VideoRenderer::~VideoRenderer() {
+}
+
+bool GtkMainWnd::VideoRenderer::SetSize(int width, int height, int reserved) {
+  gdk_threads_enter();
+  width_ = width;
+  height_ = height;
+  image_.reset(new uint8[width * height * 4]);
+  gdk_threads_leave();
+  return true;
+}
+
+bool GtkMainWnd::VideoRenderer::RenderFrame(const cricket::VideoFrame* frame) {
+  gdk_threads_enter();
+
+  int size = width_ * height_ * 4;
+  // TODO: Convert directly to RGBA
+  frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB,
+                            image_.get(),
+                            size,
+                            width_ * 4);
+  // Convert the B,G,R,A frame to R,G,B,A, which is accepted by GTK.
+  // The 'A' is just padding for GTK, so we can use it as temp.
+  uint8* pix = image_.get();
+  uint8* end = image_.get() + size;
+  while (pix < end) {
+    pix[3] = pix[0];     // Save B to A.
+    pix[0] = pix[2];  // Set Red.
+    pix[2] = pix[3];  // Set Blue.
+    pix[3] = 0xFF;     // Fixed Alpha.
+    pix += 4;
+  }
+
+  gdk_threads_leave();
+
+  g_idle_add(Redraw, main_wnd_);
+
+  return true;
+}
+
+
diff --git a/talk/examples/peerconnection/client/linux/main_wnd.h b/talk/examples/peerconnection/client/linux/main_wnd.h
new file mode 100644
index 0000000..c0f020a
--- /dev/null
+++ b/talk/examples/peerconnection/client/linux/main_wnd.h
@@ -0,0 +1,134 @@
+/*
+ * 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 PEERCONNECTION_SAMPLES_CLIENT_LINUX_MAIN_WND_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_LINUX_MAIN_WND_H_
+
+#include "talk/examples/peerconnection/client/main_wnd.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+
+// Forward declarations.
+typedef struct _GtkWidget GtkWidget;
+typedef union _GdkEvent GdkEvent;
+typedef struct _GdkEventKey GdkEventKey;
+typedef struct _GtkTreeView GtkTreeView;
+typedef struct _GtkTreePath GtkTreePath;
+typedef struct _GtkTreeViewColumn GtkTreeViewColumn;
+
+// Implements the main UI of the peer connection client.
+// This is functionally equivalent to the MainWnd class in the Windows
+// implementation.
+class GtkMainWnd : public MainWindow {
+ public:
+  GtkMainWnd();
+  ~GtkMainWnd();
+
+  virtual void RegisterObserver(MainWndCallback* callback);
+  virtual bool IsWindow();
+  virtual void SwitchToConnectUI();
+  virtual void SwitchToPeerList(const Peers& peers);
+  virtual void SwitchToStreamingUI();
+  virtual void MessageBox(const char* caption, const char* text,
+                          bool is_error);
+  virtual MainWindow::UI current_ui();
+  virtual webrtc::VideoRendererWrapperInterface* local_renderer();
+  virtual webrtc::VideoRendererWrapperInterface* remote_renderer();
+
+  virtual void QueueUIThreadCallback(int msg_id, void* data);
+
+  // Creates and shows the main window with the |Connect UI| enabled.
+  bool Create();
+
+  // Destroys the window.  When the window is destroyed, it ends the
+  // main message loop.
+  bool Destroy();
+
+  // Callback for when the main window is destroyed.
+  void OnDestroyed(GtkWidget* widget, GdkEvent* event);
+
+  // Callback for when the user clicks the "Connect" button.
+  void OnClicked(GtkWidget* widget);
+
+  // Callback for keystrokes.  Used to capture Esc and Return.
+  void OnKeyPress(GtkWidget* widget, GdkEventKey* key);
+
+  // Callback when the user double clicks a peer in order to initiate a
+  // connection.
+  void OnRowActivated(GtkTreeView* tree_view, GtkTreePath* path,
+                      GtkTreeViewColumn* column);
+
+  void OnRedraw();
+
+ protected:
+  class VideoRenderer : public cricket::VideoRenderer {
+   public:
+    explicit VideoRenderer(GtkMainWnd* main_wnd);
+    virtual ~VideoRenderer();
+
+    virtual bool SetSize(int width, int height, int reserved);
+
+    virtual bool RenderFrame(const cricket::VideoFrame* frame);
+
+    const uint8* image() const {
+      return image_.get();
+    }
+
+    int width() const {
+      return width_;
+    }
+
+    int height() const {
+      return height_;
+    }
+
+   protected:
+    talk_base::scoped_array<uint8> image_;
+    int width_;
+    int height_;
+    GtkMainWnd* main_wnd_;
+  };
+
+ protected:
+  GtkWidget* window_;  // Our main window.
+  GtkWidget* draw_area_;  // The drawing surface for rendering video streams.
+  GtkWidget* vbox_;  // Container for the Connect UI.
+  GtkWidget* server_edit_;
+  GtkWidget* port_edit_;
+  GtkWidget* peer_list_;  // The list of peers.
+  MainWndCallback* callback_;
+  std::string server_;
+  std::string port_;
+  talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+      local_renderer_wrapper_;
+  talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+      remote_renderer_wrapper_;
+  talk_base::scoped_ptr<uint8> draw_buffer_;
+  int draw_buffer_size_;
+};
+
+#endif  // PEERCONNECTION_SAMPLES_CLIENT_LINUX_MAIN_WND_H_
diff --git a/talk/examples/peerconnection/client/main.cc b/talk/examples/peerconnection/client/main.cc
new file mode 100644
index 0000000..8d2bdc9
--- /dev/null
+++ b/talk/examples/peerconnection/client/main.cc
@@ -0,0 +1,68 @@
+/*
+ * 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/examples/peerconnection/client/conductor.h"
+#include "talk/examples/peerconnection/client/main_wnd.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+#include "talk/base/win32socketinit.h"
+
+
+int PASCAL wWinMain(HINSTANCE instance, HINSTANCE prev_instance,
+                    wchar_t* cmd_line, int cmd_show) {
+  talk_base::EnsureWinsockInit();
+
+  MainWnd wnd;
+  if (!wnd.Create()) {
+    ASSERT(false);
+    return -1;
+  }
+
+  PeerConnectionClient client;
+  Conductor conductor(&client, &wnd);
+
+  // Main loop.
+  MSG msg;
+  BOOL gm;
+  while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
+    if (!wnd.PreTranslateMessage(&msg)) {
+      ::TranslateMessage(&msg);
+      ::DispatchMessage(&msg);
+    }
+  }
+
+  if (conductor.connection_active() || client.is_connected()) {
+    while ((conductor.connection_active() || client.is_connected()) &&
+           (gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
+      if (!wnd.PreTranslateMessage(&msg)) {
+        ::TranslateMessage(&msg);
+        ::DispatchMessage(&msg);
+      }
+    }
+  }
+
+  return 0;
+}
diff --git a/talk/examples/peerconnection/client/main_wnd.cc b/talk/examples/peerconnection/client/main_wnd.cc
new file mode 100644
index 0000000..367922e
--- /dev/null
+++ b/talk/examples/peerconnection/client/main_wnd.cc
@@ -0,0 +1,610 @@
+/*
+ * 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/examples/peerconnection/client/main_wnd.h"
+
+#include <math.h>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/examples/peerconnection/client/defaults.h"
+
+ATOM MainWnd::wnd_class_ = 0;
+const wchar_t MainWnd::kClassName[] = L"WebRTC_MainWnd";
+
+namespace {
+
+const char kConnecting[] = "Connecting... ";
+const char kNoVideoStreams[] = "(no video streams either way)";
+const char kNoIncomingStream[] = "(no incoming video)";
+
+void CalculateWindowSizeForText(HWND wnd, const wchar_t* text,
+                                size_t* width, size_t* height) {
+  HDC dc = ::GetDC(wnd);
+  RECT text_rc = {0};
+  ::DrawText(dc, text, -1, &text_rc, DT_CALCRECT | DT_SINGLELINE);
+  ::ReleaseDC(wnd, dc);
+  RECT client, window;
+  ::GetClientRect(wnd, &client);
+  ::GetWindowRect(wnd, &window);
+
+  *width = text_rc.right - text_rc.left;
+  *width += (window.right - window.left) -
+            (client.right - client.left);
+  *height = text_rc.bottom - text_rc.top;
+  *height += (window.bottom - window.top) -
+             (client.bottom - client.top);
+}
+
+HFONT GetDefaultFont() {
+  static HFONT font = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
+  return font;
+}
+
+std::string GetWindowText(HWND wnd) {
+  char text[MAX_PATH] = {0};
+  ::GetWindowTextA(wnd, &text[0], ARRAYSIZE(text));
+  return text;
+}
+
+void AddListBoxItem(HWND listbox, const std::string& str, LPARAM item_data) {
+  LRESULT index = ::SendMessageA(listbox, LB_ADDSTRING, 0,
+      reinterpret_cast<LPARAM>(str.c_str()));
+  ::SendMessageA(listbox, LB_SETITEMDATA, index, item_data);
+}
+
+}  // namespace
+
+MainWnd::MainWnd()
+  : ui_(CONNECT_TO_SERVER), wnd_(NULL), edit1_(NULL), edit2_(NULL),
+    label1_(NULL), label2_(NULL), button_(NULL), listbox_(NULL),
+    destroyed_(false), callback_(NULL), nested_msg_(NULL) {
+}
+
+MainWnd::~MainWnd() {
+  ASSERT(!IsWindow());
+}
+
+bool MainWnd::Create() {
+  ASSERT(wnd_ == NULL);
+  if (!RegisterWindowClass())
+    return false;
+
+  ui_thread_id_ = ::GetCurrentThreadId();
+  wnd_ = ::CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, kClassName, L"WebRTC",
+      WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
+      CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+      NULL, NULL, GetModuleHandle(NULL), this);
+
+  ::SendMessage(wnd_, WM_SETFONT, reinterpret_cast<WPARAM>(GetDefaultFont()),
+                TRUE);
+
+  CreateChildWindows();
+  SwitchToConnectUI();
+
+  return wnd_ != NULL;
+}
+
+bool MainWnd::Destroy() {
+  BOOL ret = FALSE;
+  if (IsWindow()) {
+    ret = ::DestroyWindow(wnd_);
+  }
+
+  return ret != FALSE;
+}
+
+void MainWnd::RegisterObserver(MainWndCallback* callback) {
+  callback_ = callback;
+}
+
+bool MainWnd::IsWindow() {
+  return wnd_ && ::IsWindow(wnd_) != FALSE;
+}
+
+bool MainWnd::PreTranslateMessage(MSG* msg) {
+  bool ret = false;
+  if (msg->message == WM_CHAR) {
+    if (msg->wParam == VK_TAB) {
+      HandleTabbing();
+      ret = true;
+    } else if (msg->wParam == VK_RETURN) {
+      OnDefaultAction();
+      ret = true;
+    } else if (msg->wParam == VK_ESCAPE) {
+      if (callback_) {
+        if (ui_ == STREAMING) {
+          callback_->DisconnectFromCurrentPeer();
+        } else {
+          callback_->DisconnectFromServer();
+        }
+      }
+    }
+  } else if (msg->hwnd == NULL && msg->message == UI_THREAD_CALLBACK) {
+    callback_->UIThreadCallback(static_cast<int>(msg->wParam),
+                                reinterpret_cast<void*>(msg->lParam));
+    ret = true;
+  }
+  return ret;
+}
+
+void MainWnd::SwitchToConnectUI() {
+  ASSERT(IsWindow());
+  LayoutPeerListUI(false);
+  ui_ = CONNECT_TO_SERVER;
+  LayoutConnectUI(true);
+  ::SetFocus(edit1_);
+}
+
+void MainWnd::SwitchToPeerList(const Peers& peers) {
+  // Clean up buffers from a potential previous session.
+  local_renderer_wrapper_ = NULL;
+  remote_renderer_wrapper_ = NULL;
+
+  LayoutConnectUI(false);
+
+  ::SendMessage(listbox_, LB_RESETCONTENT, 0, 0);
+
+  AddListBoxItem(listbox_, "List of currently connected peers:", -1);
+  Peers::const_iterator i = peers.begin();
+  for (; i != peers.end(); ++i)
+    AddListBoxItem(listbox_, i->second.c_str(), i->first);
+
+  ui_ = LIST_PEERS;
+  LayoutPeerListUI(true);
+  ::SetFocus(listbox_);
+}
+
+void MainWnd::SwitchToStreamingUI() {
+  LayoutConnectUI(false);
+  LayoutPeerListUI(false);
+  ui_ = STREAMING;
+}
+
+void MainWnd::MessageBox(const char* caption, const char* text, bool is_error) {
+  DWORD flags = MB_OK;
+  if (is_error)
+    flags |= MB_ICONERROR;
+
+  ::MessageBoxA(handle(), text, caption, flags);
+}
+
+webrtc::VideoRendererWrapperInterface* MainWnd::local_renderer() {
+  if (!local_renderer_wrapper_.get())
+    local_renderer_wrapper_  =
+        webrtc::CreateVideoRenderer(new VideoRenderer(handle(), 1, 1));
+  return local_renderer_wrapper_.get();
+}
+
+webrtc::VideoRendererWrapperInterface* MainWnd::remote_renderer() {
+  if (!remote_renderer_wrapper_.get())
+    remote_renderer_wrapper_ =
+        webrtc::CreateVideoRenderer(new VideoRenderer(handle(), 1, 1));
+  return remote_renderer_wrapper_.get();
+}
+
+void MainWnd::QueueUIThreadCallback(int msg_id, void* data) {
+  ::PostThreadMessage(ui_thread_id_, UI_THREAD_CALLBACK,
+      static_cast<WPARAM>(msg_id), reinterpret_cast<LPARAM>(data));
+}
+
+void MainWnd::OnPaint() {
+  PAINTSTRUCT ps;
+  ::BeginPaint(handle(), &ps);
+
+  RECT rc;
+  ::GetClientRect(handle(), &rc);
+
+  webrtc::VideoRendererWrapperInterface* renderer_wrapper = local_renderer();
+  VideoRenderer* local_renderer = renderer_wrapper ?
+      static_cast<VideoRenderer*>(renderer_wrapper->renderer()) : NULL;
+  renderer_wrapper = remote_renderer();
+  VideoRenderer* remote_renderer = renderer_wrapper ?
+      static_cast<VideoRenderer*>(renderer_wrapper->renderer()) : NULL;
+  if (ui_ == STREAMING && remote_renderer && local_renderer) {
+    AutoLock<VideoRenderer> local_lock(local_renderer);
+    AutoLock<VideoRenderer> remote_lock(remote_renderer);
+
+    const BITMAPINFO& bmi = remote_renderer->bmi();
+    int height = abs(bmi.bmiHeader.biHeight);
+    int width = bmi.bmiHeader.biWidth;
+
+    const uint8* image = remote_renderer->image();
+    if (image != NULL) {
+      HDC dc_mem = ::CreateCompatibleDC(ps.hdc);
+      ::SetStretchBltMode(dc_mem, HALFTONE);
+
+      // Set the map mode so that the ratio will be maintained for us.
+      HDC all_dc[] = { ps.hdc, dc_mem };
+      for (int i = 0; i < ARRAY_SIZE(all_dc); ++i) {
+        SetMapMode(all_dc[i], MM_ISOTROPIC);
+        SetWindowExtEx(all_dc[i], width, height, NULL);
+        SetViewportExtEx(all_dc[i], rc.right, rc.bottom, NULL);
+      }
+
+      HBITMAP bmp_mem = ::CreateCompatibleBitmap(ps.hdc, rc.right, rc.bottom);
+      HGDIOBJ bmp_old = ::SelectObject(dc_mem, bmp_mem);
+
+      POINT logical_area = { rc.right, rc.bottom };
+      DPtoLP(ps.hdc, &logical_area, 1);
+
+      HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0));
+      RECT logical_rect = {0, 0, logical_area.x, logical_area.y };
+      ::FillRect(dc_mem, &logical_rect, brush);
+      ::DeleteObject(brush);
+
+      int max_unit = (std::max)(width, height);
+      int x = (logical_area.x / 2) - (width / 2);
+      int y = (logical_area.y / 2) - (height / 2);
+
+      StretchDIBits(dc_mem, x, y, width, height,
+                    0, 0, width, height, image, &bmi, DIB_RGB_COLORS, SRCCOPY);
+
+      if ((rc.right - rc.left) > 200 && (rc.bottom - rc.top) > 200) {
+        const BITMAPINFO& bmi = local_renderer->bmi();
+        image = local_renderer->image();
+        int thumb_width = bmi.bmiHeader.biWidth / 4;
+        int thumb_height = abs(bmi.bmiHeader.biHeight) / 4;
+        StretchDIBits(dc_mem,
+            logical_area.x - thumb_width - 10,
+            logical_area.y - thumb_height - 10,
+            thumb_width, thumb_height,
+            0, 0, bmi.bmiHeader.biWidth, -bmi.bmiHeader.biHeight,
+            image, &bmi, DIB_RGB_COLORS, SRCCOPY);
+      }
+
+      BitBlt(ps.hdc, 0, 0, logical_area.x, logical_area.y,
+             dc_mem, 0, 0, SRCCOPY);
+
+      // Cleanup.
+      ::SelectObject(dc_mem, bmp_old);
+      ::DeleteObject(bmp_mem);
+      ::DeleteDC(dc_mem);
+    } else {
+      // We're still waiting for the video stream to be initialized.
+      HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0));
+      ::FillRect(ps.hdc, &rc, brush);
+      ::DeleteObject(brush);
+
+      HGDIOBJ old_font = ::SelectObject(ps.hdc, GetDefaultFont());
+      ::SetTextColor(ps.hdc, RGB(0xff, 0xff, 0xff));
+      ::SetBkMode(ps.hdc, TRANSPARENT);
+
+      std::string text(kConnecting);
+      if (!local_renderer->image()) {
+        text += kNoVideoStreams;
+      } else {
+        text += kNoIncomingStream;
+      }
+      ::DrawTextA(ps.hdc, text.c_str(), -1, &rc,
+          DT_SINGLELINE | DT_CENTER | DT_VCENTER);
+      ::SelectObject(ps.hdc, old_font);
+    }
+  } else {
+    HBRUSH brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
+    ::FillRect(ps.hdc, &rc, brush);
+    ::DeleteObject(brush);
+  }
+
+  ::EndPaint(handle(), &ps);
+}
+
+void MainWnd::OnDestroyed() {
+  PostQuitMessage(0);
+}
+
+void MainWnd::OnDefaultAction() {
+  if (!callback_)
+    return;
+  if (ui_ == CONNECT_TO_SERVER) {
+    std::string server(GetWindowText(edit1_));
+    std::string port_str(GetWindowText(edit2_));
+    int port = port_str.length() ? atoi(port_str.c_str()) : 0;
+    callback_->StartLogin(server, port);
+  } else if (ui_ == LIST_PEERS) {
+    LRESULT sel = ::SendMessage(listbox_, LB_GETCURSEL, 0, 0);
+    if (sel != LB_ERR) {
+      LRESULT peer_id = ::SendMessage(listbox_, LB_GETITEMDATA, sel, 0);
+      if (peer_id != -1 && callback_) {
+        callback_->ConnectToPeer(peer_id);
+      }
+    }
+  } else {
+    MessageBoxA(wnd_, "OK!", "Yeah", MB_OK);
+  }
+}
+
+bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) {
+  switch (msg) {
+    case WM_ERASEBKGND:
+      *result = TRUE;
+      return true;
+
+    case WM_PAINT:
+      OnPaint();
+      return true;
+
+    case WM_SETFOCUS:
+      if (ui_ == CONNECT_TO_SERVER) {
+        SetFocus(edit1_);
+      } else if (ui_ == LIST_PEERS) {
+        SetFocus(listbox_);
+      }
+      return true;
+
+    case WM_SIZE:
+      if (ui_ == CONNECT_TO_SERVER) {
+        LayoutConnectUI(true);
+      } else if (ui_ == LIST_PEERS) {
+        LayoutPeerListUI(true);
+      }
+      break;
+
+    case WM_CTLCOLORSTATIC:
+      *result = reinterpret_cast<LRESULT>(GetSysColorBrush(COLOR_WINDOW));
+      return true;
+
+    case WM_COMMAND:
+      if (button_ == reinterpret_cast<HWND>(lp)) {
+        if (BN_CLICKED == HIWORD(wp))
+          OnDefaultAction();
+      } else if (listbox_ == reinterpret_cast<HWND>(lp)) {
+        if (LBN_DBLCLK == HIWORD(wp)) {
+          OnDefaultAction();
+        }
+      }
+      return true;
+
+    case WM_CLOSE:
+      if (callback_)
+        callback_->Close();
+      break;
+  }
+  return false;
+}
+
+// static
+LRESULT CALLBACK MainWnd::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
+  MainWnd* me = reinterpret_cast<MainWnd*>(
+      ::GetWindowLongPtr(hwnd, GWL_USERDATA));
+  if (!me && WM_CREATE == msg) {
+    CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lp);
+    me = reinterpret_cast<MainWnd*>(cs->lpCreateParams);
+    me->wnd_ = hwnd;
+    ::SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(me));
+  }
+
+  LRESULT result = 0;
+  if (me) {
+    void* prev_nested_msg = me->nested_msg_;
+    me->nested_msg_ = &msg;
+
+    bool handled = me->OnMessage(msg, wp, lp, &result);
+    if (WM_NCDESTROY == msg) {
+      me->destroyed_ = true;
+    } else if (!handled) {
+      result = ::DefWindowProc(hwnd, msg, wp, lp);
+    }
+
+    if (me->destroyed_ && prev_nested_msg == NULL) {
+      me->OnDestroyed();
+      me->wnd_ = NULL;
+      me->destroyed_ = false;
+    }
+
+    me->nested_msg_ = prev_nested_msg;
+  } else {
+    result = ::DefWindowProc(hwnd, msg, wp, lp);
+  }
+
+  return result;
+}
+
+// static
+bool MainWnd::RegisterWindowClass() {
+  if (wnd_class_)
+    return true;
+
+  WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
+  wcex.style = CS_DBLCLKS;
+  wcex.hInstance = GetModuleHandle(NULL);
+  wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
+  wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW);
+  wcex.lpfnWndProc = &WndProc;
+  wcex.lpszClassName = kClassName;
+  wnd_class_ = ::RegisterClassEx(&wcex);
+  ASSERT(wnd_class_ != 0);
+  return wnd_class_ != 0;
+}
+
+void MainWnd::CreateChildWindow(HWND* wnd, MainWnd::ChildWindowID id,
+                                const wchar_t* class_name, DWORD control_style,
+                                DWORD ex_style) {
+  if (::IsWindow(*wnd))
+    return;
+
+  // Child windows are invisible at first, and shown after being resized.
+  DWORD style = WS_CHILD | control_style;
+  *wnd = ::CreateWindowEx(ex_style, class_name, L"", style,
+                          100, 100, 100, 100, wnd_,
+                          reinterpret_cast<HMENU>(id),
+                          GetModuleHandle(NULL), NULL);
+  ASSERT(::IsWindow(*wnd) != FALSE);
+  ::SendMessage(*wnd, WM_SETFONT, reinterpret_cast<WPARAM>(GetDefaultFont()),
+                TRUE);
+}
+
+void MainWnd::CreateChildWindows() {
+  // Create the child windows in tab order.
+  CreateChildWindow(&label1_, LABEL1_ID, L"Static", ES_CENTER | ES_READONLY, 0);
+  CreateChildWindow(&edit1_, EDIT_ID, L"Edit",
+                    ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE);
+  CreateChildWindow(&label2_, LABEL2_ID, L"Static", ES_CENTER | ES_READONLY, 0);
+  CreateChildWindow(&edit2_, EDIT_ID, L"Edit",
+                    ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE);
+  CreateChildWindow(&button_, BUTTON_ID, L"Button", BS_CENTER | WS_TABSTOP, 0);
+
+  CreateChildWindow(&listbox_, LISTBOX_ID, L"ListBox",
+                    LBS_HASSTRINGS | LBS_NOTIFY, WS_EX_CLIENTEDGE);
+
+  ::SetWindowTextA(edit1_, GetDefaultServerName().c_str());
+  ::SetWindowTextA(edit2_, "8888");
+}
+
+void MainWnd::LayoutConnectUI(bool show) {
+  struct Windows {
+    HWND wnd;
+    const wchar_t* text;
+    size_t width;
+    size_t height;
+  } windows[] = {
+    { label1_, L"Server" },
+    { edit1_, L"XXXyyyYYYgggXXXyyyYYYggg" },
+    { label2_, L":" },
+    { edit2_, L"XyXyX" },
+    { button_, L"Connect" },
+  };
+
+  if (show) {
+    const size_t kSeparator = 5;
+    size_t total_width = (ARRAYSIZE(windows) - 1) * kSeparator;
+
+    for (size_t i = 0; i < ARRAYSIZE(windows); ++i) {
+      CalculateWindowSizeForText(windows[i].wnd, windows[i].text,
+                                 &windows[i].width, &windows[i].height);
+      total_width += windows[i].width;
+    }
+
+    RECT rc;
+    ::GetClientRect(wnd_, &rc);
+    size_t x = (rc.right / 2) - (total_width / 2);
+    size_t y = rc.bottom / 2;
+    for (size_t i = 0; i < ARRAYSIZE(windows); ++i) {
+      size_t top = y - (windows[i].height / 2);
+      ::MoveWindow(windows[i].wnd, x, top, windows[i].width, windows[i].height,
+                   TRUE);
+      x += kSeparator + windows[i].width;
+      if (windows[i].text[0] != 'X')
+        ::SetWindowText(windows[i].wnd, windows[i].text);
+      ::ShowWindow(windows[i].wnd, SW_SHOWNA);
+    }
+  } else {
+    for (size_t i = 0; i < ARRAYSIZE(windows); ++i) {
+      ::ShowWindow(windows[i].wnd, SW_HIDE);
+    }
+  }
+}
+
+void MainWnd::LayoutPeerListUI(bool show) {
+  if (show) {
+    RECT rc;
+    ::GetClientRect(wnd_, &rc);
+    ::MoveWindow(listbox_, 0, 0, rc.right, rc.bottom, TRUE);
+    ::ShowWindow(listbox_, SW_SHOWNA);
+  } else {
+    ::ShowWindow(listbox_, SW_HIDE);
+    InvalidateRect(wnd_, NULL, TRUE);
+  }
+}
+
+void MainWnd::HandleTabbing() {
+  bool shift = ((::GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0);
+  UINT next_cmd = shift ? GW_HWNDPREV : GW_HWNDNEXT;
+  UINT loop_around_cmd = shift ? GW_HWNDLAST : GW_HWNDFIRST;
+  HWND focus = GetFocus(), next;
+  do {
+    next = ::GetWindow(focus, next_cmd);
+    if (IsWindowVisible(next) &&
+        (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) {
+      break;
+    }
+
+    if (!next) {
+      next = ::GetWindow(focus, loop_around_cmd);
+      if (IsWindowVisible(next) &&
+          (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) {
+        break;
+      }
+    }
+    focus = next;
+  } while (true);
+  ::SetFocus(next);
+}
+
+//
+// MainWnd::VideoRenderer
+//
+
+MainWnd::VideoRenderer::VideoRenderer(HWND wnd, int width, int height)
+    : wnd_(wnd) {
+  ::InitializeCriticalSection(&buffer_lock_);
+  ZeroMemory(&bmi_, sizeof(bmi_));
+  bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+  bmi_.bmiHeader.biPlanes = 1;
+  bmi_.bmiHeader.biBitCount = 32;
+  bmi_.bmiHeader.biCompression = BI_RGB;
+  bmi_.bmiHeader.biWidth = width;
+  bmi_.bmiHeader.biHeight = -height;
+  bmi_.bmiHeader.biSizeImage = width * height *
+                              (bmi_.bmiHeader.biBitCount >> 3);
+}
+
+MainWnd::VideoRenderer::~VideoRenderer() {
+  ::DeleteCriticalSection(&buffer_lock_);
+}
+
+bool MainWnd::VideoRenderer::SetSize(int width, int height, int reserved) {
+  AutoLock<VideoRenderer> lock(this);
+
+  bmi_.bmiHeader.biWidth = width;
+  bmi_.bmiHeader.biHeight = -height;
+  bmi_.bmiHeader.biSizeImage = width * height *
+                               (bmi_.bmiHeader.biBitCount >> 3);
+  image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]);
+
+  return true;
+}
+
+bool MainWnd::VideoRenderer::RenderFrame(const cricket::VideoFrame* frame) {
+  if (!frame)
+    return false;
+
+  {
+    AutoLock<VideoRenderer> lock(this);
+
+    ASSERT(image_.get() != NULL);
+    frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB,
+                              image_.get(),
+                              bmi_.bmiHeader.biSizeImage,
+                              bmi_.bmiHeader.biWidth *
+                                bmi_.bmiHeader.biBitCount / 8);
+  }
+
+  InvalidateRect(wnd_, NULL, TRUE);
+
+  return true;
+}
diff --git a/talk/examples/peerconnection/client/main_wnd.h b/talk/examples/peerconnection/client/main_wnd.h
new file mode 100644
index 0000000..17243b3
--- /dev/null
+++ b/talk/examples/peerconnection/client/main_wnd.h
@@ -0,0 +1,210 @@
+/*
+ * 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 PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+#include "talk/base/win32.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/videoframe.h"
+#include "talk/session/phone/videorenderer.h"
+
+class MainWndCallback {
+ public:
+  virtual bool StartLogin(const std::string& server, int port) = 0;
+  virtual void DisconnectFromServer() = 0;
+  virtual void ConnectToPeer(int peer_id) = 0;
+  virtual void DisconnectFromCurrentPeer() = 0;
+  virtual void UIThreadCallback(int msg_id, void* data) = 0;
+  virtual void Close() = 0;
+ protected:
+  virtual ~MainWndCallback() {}
+};
+
+// Pure virtual interface for the main window.
+class MainWindow {
+ public:
+  virtual ~MainWindow() {}
+
+  enum UI {
+    CONNECT_TO_SERVER,
+    LIST_PEERS,
+    STREAMING,
+  };
+
+  virtual void RegisterObserver(MainWndCallback* callback) = 0;
+
+  virtual bool IsWindow() = 0;
+  virtual void MessageBox(const char* caption, const char* text,
+                          bool is_error) = 0;
+
+  virtual UI current_ui() = 0;
+
+  virtual void SwitchToConnectUI() = 0;
+  virtual void SwitchToPeerList(const Peers& peers) = 0;
+  virtual void SwitchToStreamingUI() = 0;
+
+  virtual webrtc::VideoRendererWrapperInterface* local_renderer() = 0;
+  virtual webrtc::VideoRendererWrapperInterface* remote_renderer() = 0;
+
+  virtual void QueueUIThreadCallback(int msg_id, void* data) = 0;
+};
+
+#ifdef WIN32
+
+class MainWnd : public MainWindow {
+ public:
+  static const wchar_t kClassName[];
+
+  enum WindowMessages {
+    UI_THREAD_CALLBACK = WM_APP + 1,
+  };
+
+  MainWnd();
+  ~MainWnd();
+
+  bool Create();
+  bool Destroy();
+  bool PreTranslateMessage(MSG* msg);
+
+  virtual void RegisterObserver(MainWndCallback* callback);
+  virtual bool IsWindow();
+  virtual void SwitchToConnectUI();
+  virtual void SwitchToPeerList(const Peers& peers);
+  virtual void SwitchToStreamingUI();
+  virtual void MessageBox(const char* caption, const char* text,
+                          bool is_error);
+  virtual UI current_ui() { return ui_; }
+
+  virtual webrtc::VideoRendererWrapperInterface* local_renderer();
+  virtual webrtc::VideoRendererWrapperInterface* remote_renderer();
+
+  virtual void QueueUIThreadCallback(int msg_id, void* data);
+
+  HWND handle() const { return wnd_; }
+
+  class VideoRenderer : public cricket::VideoRenderer {
+   public:
+    VideoRenderer(HWND wnd, int width, int height);
+    virtual ~VideoRenderer();
+
+    void Lock() {
+      ::EnterCriticalSection(&buffer_lock_);
+    }
+
+    void Unlock() {
+      ::LeaveCriticalSection(&buffer_lock_);
+    }
+
+    virtual bool SetSize(int width, int height, int reserved);
+
+    // Called when a new frame is available for display.
+    virtual bool RenderFrame(const cricket::VideoFrame* frame);
+
+    const BITMAPINFO& bmi() const { return bmi_; }
+    const uint8* image() const { return image_.get(); }
+
+   protected:
+    enum {
+      SET_SIZE,
+      RENDER_FRAME,
+    };
+
+    HWND wnd_;
+    BITMAPINFO bmi_;
+    talk_base::scoped_array<uint8> image_;
+    CRITICAL_SECTION buffer_lock_;
+  };
+
+  // A little helper class to make sure we always to proper locking and
+  // unlocking when working with VideoRenderer buffers.
+  template <typename T>
+  class AutoLock {
+   public:
+    explicit AutoLock(T* obj) : obj_(obj) { obj_->Lock(); }
+    ~AutoLock() { obj_->Unlock(); }
+   protected:
+    T* obj_;
+  };
+
+ protected:
+  enum ChildWindowID {
+    EDIT_ID = 1,
+    BUTTON_ID,
+    LABEL1_ID,
+    LABEL2_ID,
+    LISTBOX_ID,
+  };
+
+  void OnPaint();
+  void OnDestroyed();
+
+  void OnDefaultAction();
+
+  bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result);
+
+  static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
+  static bool RegisterWindowClass();
+
+  void CreateChildWindow(HWND* wnd, ChildWindowID id, const wchar_t* class_name,
+                         DWORD control_style, DWORD ex_style);
+  void CreateChildWindows();
+
+  void LayoutConnectUI(bool show);
+  void LayoutPeerListUI(bool show);
+
+  void HandleTabbing();
+
+ private:
+  talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+      local_renderer_wrapper_;
+  talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+      remote_renderer_wrapper_;
+  UI ui_;
+  HWND wnd_;
+  DWORD ui_thread_id_;
+  HWND edit1_;
+  HWND edit2_;
+  HWND label1_;
+  HWND label2_;
+  HWND button_;
+  HWND listbox_;
+  bool destroyed_;
+  void* nested_msg_;
+  MainWndCallback* callback_;
+  static ATOM wnd_class_;
+};
+#endif  // WIN32
+
+#endif  // PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_
diff --git a/talk/examples/peerconnection/client/peer_connection_client.cc b/talk/examples/peerconnection/client/peer_connection_client.cc
new file mode 100644
index 0000000..64706de
--- /dev/null
+++ b/talk/examples/peerconnection/client/peer_connection_client.cc
@@ -0,0 +1,499 @@
+/*
+ * 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/examples/peerconnection/client/peer_connection_client.h"
+
+#include "talk/examples/peerconnection/client/defaults.h"
+#include "talk/base/common.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+
+#ifdef WIN32
+#include "talk/base/win32socketserver.h"
+#endif
+
+using talk_base::sprintfn;
+
+namespace {
+
+// This is our magical hangup signal.
+const char kByeMessage[] = "BYE";
+
+talk_base::AsyncSocket* CreateClientSocket() {
+#ifdef WIN32
+  return new talk_base::Win32Socket();
+#elif defined(POSIX)
+  talk_base::Thread* thread = talk_base::Thread::Current();
+  ASSERT(thread != NULL);
+  return thread->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+#else
+#error Platform not supported.
+#endif
+}
+
+}
+
+PeerConnectionClient::PeerConnectionClient()
+  : callback_(NULL),
+    control_socket_(CreateClientSocket()),
+    hanging_get_(CreateClientSocket()),
+    state_(NOT_CONNECTED),
+    my_id_(-1) {
+  control_socket_->SignalCloseEvent.connect(this,
+      &PeerConnectionClient::OnClose);
+  hanging_get_->SignalCloseEvent.connect(this,
+      &PeerConnectionClient::OnClose);
+  control_socket_->SignalConnectEvent.connect(this,
+      &PeerConnectionClient::OnConnect);
+  hanging_get_->SignalConnectEvent.connect(this,
+      &PeerConnectionClient::OnHangingGetConnect);
+  control_socket_->SignalReadEvent.connect(this,
+      &PeerConnectionClient::OnRead);
+  hanging_get_->SignalReadEvent.connect(this,
+      &PeerConnectionClient::OnHangingGetRead);
+}
+
+PeerConnectionClient::~PeerConnectionClient() {
+}
+
+int PeerConnectionClient::id() const {
+  return my_id_;
+}
+
+bool PeerConnectionClient::is_connected() const {
+  return my_id_ != -1;
+}
+
+const Peers& PeerConnectionClient::peers() const {
+  return peers_;
+}
+
+void PeerConnectionClient::RegisterObserver(
+    PeerConnectionClientObserver* callback) {
+  ASSERT(!callback_);
+  callback_ = callback;
+}
+
+bool PeerConnectionClient::Connect(const std::string& server, int port,
+                                   const std::string& client_name) {
+  ASSERT(!server.empty());
+  ASSERT(!client_name.empty());
+
+  if (state_ != NOT_CONNECTED) {
+    LOG(WARNING)
+        << "The client must not be connected before you can call Connect()";
+    return false;
+  }
+
+  if (server.empty() || client_name.empty())
+    return false;
+
+  if (port <= 0)
+    port = kDefaultServerPort;
+
+  server_address_.SetIP(server);
+  server_address_.SetPort(port);
+
+  if (server_address_.IsUnresolved()) {
+    int errcode = 0;
+    hostent* h = talk_base::SafeGetHostByName(
+          server_address_.IPAsString().c_str(), &errcode);
+    if (!h) {
+      LOG(LS_ERROR) << "Failed to resolve host name: "
+                    << server_address_.IPAsString();
+      return false;
+    } else {
+      server_address_.SetResolvedIP(
+          ntohl(*reinterpret_cast<uint32*>(h->h_addr_list[0])));
+      talk_base::FreeHostEnt(h);
+    }
+  }
+
+  char buffer[1024];
+  sprintfn(buffer, sizeof(buffer),
+           "GET /sign_in?%s HTTP/1.0\r\n\r\n", client_name.c_str());
+  onconnect_data_ = buffer;
+
+  bool ret = ConnectControlSocket();
+  if (ret)
+    state_ = SIGNING_IN;
+
+  return ret;
+}
+
+bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) {
+  if (state_ != CONNECTED)
+    return false;
+
+  ASSERT(is_connected());
+  ASSERT(control_socket_->GetState() == talk_base::Socket::CS_CLOSED);
+  if (!is_connected() || peer_id == -1)
+    return false;
+
+  char headers[1024];
+  sprintfn(headers, sizeof(headers),
+      "POST /message?peer_id=%i&to=%i HTTP/1.0\r\n"
+      "Content-Length: %i\r\n"
+      "Content-Type: text/plain\r\n"
+      "\r\n",
+      my_id_, peer_id, message.length());
+  onconnect_data_ = headers;
+  onconnect_data_ += message;
+  return ConnectControlSocket();
+}
+
+bool PeerConnectionClient::SendHangUp(int peer_id) {
+  return SendToPeer(peer_id, kByeMessage);
+}
+
+bool PeerConnectionClient::IsSendingMessage() {
+  return state_ == CONNECTED &&
+         control_socket_->GetState() != talk_base::Socket::CS_CLOSED;
+}
+
+bool PeerConnectionClient::SignOut() {
+  if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT)
+    return true;
+
+  if (hanging_get_->GetState() != talk_base::Socket::CS_CLOSED)
+    hanging_get_->Close();
+
+  if (control_socket_->GetState() == talk_base::Socket::CS_CLOSED) {
+    state_ = SIGNING_OUT;
+
+    if (my_id_ != -1) {
+      char buffer[1024];
+      sprintfn(buffer, sizeof(buffer),
+          "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
+      onconnect_data_ = buffer;
+      return ConnectControlSocket();
+    } else {
+      // Can occur if the app is closed before we finish connecting.
+      return true;
+    }
+  } else {
+    state_ = SIGNING_OUT_WAITING;
+  }
+
+  return true;
+}
+
+void PeerConnectionClient::Close() {
+  control_socket_->Close();
+  hanging_get_->Close();
+  onconnect_data_.clear();
+  peers_.clear();
+  my_id_ = -1;
+  state_ = NOT_CONNECTED;
+}
+
+bool PeerConnectionClient::ConnectControlSocket() {
+  ASSERT(control_socket_->GetState() == talk_base::Socket::CS_CLOSED);
+  int err = control_socket_->Connect(server_address_);
+  if (err == SOCKET_ERROR) {
+    Close();
+    return false;
+  }
+  return true;
+}
+
+void PeerConnectionClient::OnConnect(talk_base::AsyncSocket* socket) {
+  ASSERT(!onconnect_data_.empty());
+  size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length());
+  ASSERT(sent == onconnect_data_.length());
+  UNUSED(sent);
+  onconnect_data_.clear();
+}
+
+void PeerConnectionClient::OnHangingGetConnect(talk_base::AsyncSocket* socket) {
+  char buffer[1024];
+  sprintfn(buffer, sizeof(buffer),
+           "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
+  int len = strlen(buffer);
+  int sent = socket->Send(buffer, len);
+  ASSERT(sent == len);
+  UNUSED2(sent, len);
+}
+
+void PeerConnectionClient::OnMessageFromPeer(int peer_id,
+                                             const std::string& message) {
+  if (message.length() == (sizeof(kByeMessage) - 1) &&
+      message.compare(kByeMessage) == 0) {
+    callback_->OnPeerDisconnected(peer_id);
+  } else {
+    callback_->OnMessageFromPeer(peer_id, message);
+  }
+}
+
+bool PeerConnectionClient::GetHeaderValue(const std::string& data,
+                                          size_t eoh,
+                                          const char* header_pattern,
+                                          size_t* value) {
+  ASSERT(value != NULL);
+  size_t found = data.find(header_pattern);
+  if (found != std::string::npos && found < eoh) {
+    *value = atoi(&data[found + strlen(header_pattern)]);
+    return true;
+  }
+  return false;
+}
+
+bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh,
+                                          const char* header_pattern,
+                                          std::string* value) {
+  ASSERT(value != NULL);
+  size_t found = data.find(header_pattern);
+  if (found != std::string::npos && found < eoh) {
+    size_t begin = found + strlen(header_pattern);
+    size_t end = data.find("\r\n", begin);
+    if (end == std::string::npos)
+      end = eoh;
+    value->assign(data.substr(begin, end - begin));
+    return true;
+  }
+  return false;
+}
+
+bool PeerConnectionClient::ReadIntoBuffer(talk_base::AsyncSocket* socket,
+                                          std::string* data,
+                                          size_t* content_length) {
+  LOG(INFO) << __FUNCTION__;
+
+  char buffer[0xffff];
+  do {
+    int bytes = socket->Recv(buffer, sizeof(buffer));
+    if (bytes <= 0)
+      break;
+    data->append(buffer, bytes);
+  } while (true);
+
+  bool ret = false;
+  size_t i = data->find("\r\n\r\n");
+  if (i != std::string::npos) {
+    LOG(INFO) << "Headers received";
+    if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) {
+      LOG(INFO) << "Expecting " << *content_length << " bytes.";
+      size_t total_response_size = (i + 4) + *content_length;
+      if (data->length() >= total_response_size) {
+        ret = true;
+        std::string should_close;
+        const char kConnection[] = "\r\nConnection: ";
+        if (GetHeaderValue(*data, i, kConnection, &should_close) &&
+            should_close.compare("close") == 0) {
+          socket->Close();
+          // Since we closed the socket, there was no notification delivered
+          // to us.  Compensate by letting ourselves know.
+          OnClose(socket, 0);
+        }
+      } else {
+        // We haven't received everything.  Just continue to accept data.
+      }
+    } else {
+      LOG(LS_ERROR) << "No content length field specified by the server.";
+    }
+  }
+  return ret;
+}
+
+void PeerConnectionClient::OnRead(talk_base::AsyncSocket* socket) {
+  LOG(INFO) << __FUNCTION__;
+  size_t content_length = 0;
+  if (ReadIntoBuffer(socket, &control_data_, &content_length)) {
+    size_t peer_id = 0, eoh = 0;
+    bool ok = ParseServerResponse(control_data_, content_length, &peer_id,
+                                  &eoh);
+    if (ok) {
+      if (my_id_ == -1) {
+        // First response.  Let's store our server assigned ID.
+        ASSERT(state_ == SIGNING_IN);
+        my_id_ = peer_id;
+        ASSERT(my_id_ != -1);
+
+        // The body of the response will be a list of already connected peers.
+        if (content_length) {
+          size_t pos = eoh + 4;
+          while (pos < control_data_.size()) {
+            size_t eol = control_data_.find('\n', pos);
+            if (eol == std::string::npos)
+              break;
+            int id = 0;
+            std::string name;
+            bool connected;
+            if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id,
+                           &connected) && id != my_id_) {
+              peers_[id] = name;
+              callback_->OnPeerConnected(id, name);
+            }
+            pos = eol + 1;
+          }
+        }
+        ASSERT(is_connected());
+        callback_->OnSignedIn();
+      } else if (state_ == SIGNING_OUT) {
+        Close();
+        callback_->OnDisconnected();
+      } else if (state_ == SIGNING_OUT_WAITING) {
+        SignOut();
+      }
+    }
+
+    control_data_.clear();
+
+    if (state_ == SIGNING_IN) {
+      ASSERT(hanging_get_->GetState() == talk_base::Socket::CS_CLOSED);
+      state_ = CONNECTED;
+      hanging_get_->Connect(server_address_);
+    }
+  }
+}
+
+void PeerConnectionClient::OnHangingGetRead(talk_base::AsyncSocket* socket) {
+  LOG(INFO) << __FUNCTION__;
+  size_t content_length = 0;
+  if (ReadIntoBuffer(socket, &notification_data_, &content_length)) {
+    size_t peer_id = 0, eoh = 0;
+    bool ok = ParseServerResponse(notification_data_, content_length,
+                                  &peer_id, &eoh);
+
+    if (ok) {
+      // Store the position where the body begins.
+      size_t pos = eoh + 4;
+
+      if (my_id_ == static_cast<int>(peer_id)) {
+        // A notification about a new member or a member that just
+        // disconnected.
+        int id = 0;
+        std::string name;
+        bool connected = false;
+        if (ParseEntry(notification_data_.substr(pos), &name, &id,
+                       &connected)) {
+          if (connected) {
+            peers_[id] = name;
+            callback_->OnPeerConnected(id, name);
+          } else {
+            peers_.erase(id);
+            callback_->OnPeerDisconnected(id);
+          }
+        }
+      } else {
+        OnMessageFromPeer(peer_id, notification_data_.substr(pos));
+      }
+    }
+
+    notification_data_.clear();
+  }
+
+  if (hanging_get_->GetState() == talk_base::Socket::CS_CLOSED &&
+      state_ == CONNECTED) {
+    hanging_get_->Connect(server_address_);
+  }
+}
+
+bool PeerConnectionClient::ParseEntry(const std::string& entry,
+                                      std::string* name,
+                                      int* id,
+                                      bool* connected) {
+  ASSERT(name != NULL);
+  ASSERT(id != NULL);
+  ASSERT(connected != NULL);
+  ASSERT(!entry.empty());
+
+  *connected = false;
+  size_t separator = entry.find(',');
+  if (separator != std::string::npos) {
+    *id = atoi(&entry[separator + 1]);
+    name->assign(entry.substr(0, separator));
+    separator = entry.find(',', separator + 1);
+    if (separator != std::string::npos) {
+      *connected = atoi(&entry[separator + 1]) ? true : false;
+    }
+  }
+  return !name->empty();
+}
+
+int PeerConnectionClient::GetResponseStatus(const std::string& response) {
+  int status = -1;
+  size_t pos = response.find(' ');
+  if (pos != std::string::npos)
+    status = atoi(&response[pos + 1]);
+  return status;
+}
+
+bool PeerConnectionClient::ParseServerResponse(const std::string& response,
+                                               size_t content_length,
+                                               size_t* peer_id,
+                                               size_t* eoh) {
+  LOG(INFO) << response;
+
+  int status = GetResponseStatus(response.c_str());
+  if (status != 200) {
+    LOG(LS_ERROR) << "Received error from server";
+    Close();
+    callback_->OnDisconnected();
+    return false;
+  }
+
+  *eoh = response.find("\r\n\r\n");
+  ASSERT(*eoh != std::string::npos);
+  if (*eoh == std::string::npos)
+    return false;
+
+  *peer_id = -1;
+
+  // See comment in peer_channel.cc for why we use the Pragma header and
+  // not e.g. "X-Peer-Id".
+  GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id);
+
+  return true;
+}
+
+void PeerConnectionClient::OnClose(talk_base::AsyncSocket* socket, int err) {
+  LOG(INFO) << __FUNCTION__;
+
+  socket->Close();
+
+#ifdef WIN32
+  if (err != WSAECONNREFUSED) {
+#else
+  if (err != ECONNREFUSED) {
+#endif
+    if (socket == hanging_get_.get()) {
+      if (state_ == CONNECTED) {
+        LOG(INFO) << "Issuing  a new hanging get";
+        hanging_get_->Close();
+        hanging_get_->Connect(server_address_);
+      }
+    } else {
+      callback_->OnMessageSent(err);
+    }
+  } else {
+    LOG(WARNING) << "Failed to connect to the server";
+    Close();
+    callback_->OnDisconnected();
+  }
+}
diff --git a/talk/examples/peerconnection/client/peer_connection_client.h b/talk/examples/peerconnection/client/peer_connection_client.h
new file mode 100644
index 0000000..f47a24d
--- /dev/null
+++ b/talk/examples/peerconnection/client/peer_connection_client.h
@@ -0,0 +1,126 @@
+/*
+ * 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 PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+
+typedef std::map<int, std::string> Peers;
+
+struct PeerConnectionClientObserver {
+  virtual void OnSignedIn() = 0;  // Called when we're logged on.
+  virtual void OnDisconnected() = 0;
+  virtual void OnPeerConnected(int id, const std::string& name) = 0;
+  virtual void OnPeerDisconnected(int peer_id) = 0;
+  virtual void OnMessageFromPeer(int peer_id, const std::string& message) = 0;
+  virtual void OnMessageSent(int err) = 0;
+
+ protected:
+  virtual ~PeerConnectionClientObserver() {}
+};
+
+class PeerConnectionClient : public sigslot::has_slots<> {
+ public:
+  enum State {
+    NOT_CONNECTED,
+    SIGNING_IN,
+    CONNECTED,
+    SIGNING_OUT_WAITING,
+    SIGNING_OUT,
+  };
+
+  PeerConnectionClient();
+  ~PeerConnectionClient();
+
+  int id() const;
+  bool is_connected() const;
+  const Peers& peers() const;
+
+  void RegisterObserver(PeerConnectionClientObserver* callback);
+
+  bool Connect(const std::string& server, int port,
+               const std::string& client_name);
+
+  bool SendToPeer(int peer_id, const std::string& message);
+  bool SendHangUp(int peer_id);
+  bool IsSendingMessage();
+
+  bool SignOut();
+
+ protected:
+  void Close();
+  bool ConnectControlSocket();
+  void OnConnect(talk_base::AsyncSocket* socket);
+  void OnHangingGetConnect(talk_base::AsyncSocket* socket);
+  void OnMessageFromPeer(int peer_id, const std::string& message);
+
+  // Quick and dirty support for parsing HTTP header values.
+  bool GetHeaderValue(const std::string& data, size_t eoh,
+                      const char* header_pattern, size_t* value);
+
+  bool GetHeaderValue(const std::string& data, size_t eoh,
+                      const char* header_pattern, std::string* value);
+
+  // Returns true if the whole response has been read.
+  bool ReadIntoBuffer(talk_base::AsyncSocket* socket, std::string* data,
+                      size_t* content_length);
+
+  void OnRead(talk_base::AsyncSocket* socket);
+
+  void OnHangingGetRead(talk_base::AsyncSocket* socket);
+
+  // Parses a single line entry in the form "<name>,<id>,<connected>"
+  bool ParseEntry(const std::string& entry, std::string* name, int* id,
+                  bool* connected);
+
+  int GetResponseStatus(const std::string& response);
+
+  bool ParseServerResponse(const std::string& response, size_t content_length,
+                           size_t* peer_id, size_t* eoh);
+
+  void OnClose(talk_base::AsyncSocket* socket, int err);
+
+  PeerConnectionClientObserver* callback_;
+  talk_base::SocketAddress server_address_;
+  talk_base::scoped_ptr<talk_base::AsyncSocket> control_socket_;
+  talk_base::scoped_ptr<talk_base::AsyncSocket> hanging_get_;
+  std::string onconnect_data_;
+  std::string control_data_;
+  std::string notification_data_;
+  Peers peers_;
+  State state_;
+  int my_id_;
+};
+
+#endif  // PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_
diff --git a/talk/examples/peerconnection/server/data_socket.cc b/talk/examples/peerconnection/server/data_socket.cc
new file mode 100644
index 0000000..81cba2e
--- /dev/null
+++ b/talk/examples/peerconnection/server/data_socket.cc
@@ -0,0 +1,304 @@
+/*
+ * 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/examples/peerconnection/server/data_socket.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "talk/examples/peerconnection/server/utils.h"
+
+static const char kHeaderTerminator[] = "\r\n\r\n";
+static const int kHeaderTerminatorLength = sizeof(kHeaderTerminator) - 1;
+
+// static
+const char DataSocket::kCrossOriginAllowHeaders[] =
+    "Access-Control-Allow-Origin: *\r\n"
+    "Access-Control-Allow-Credentials: true\r\n"
+    "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"
+    "Access-Control-Allow-Headers: Content-Type, "
+        "Content-Length, Connection, Cache-Control\r\n"
+    "Access-Control-Expose-Headers: Content-Length, X-Peer-Id\r\n";
+
+#if defined(WIN32)
+class WinsockInitializer {
+  static WinsockInitializer singleton;
+
+  WinsockInitializer() {
+    WSADATA data;
+    WSAStartup(MAKEWORD(1, 0), &data);
+  }
+
+ public:
+  ~WinsockInitializer() { WSACleanup(); }
+};
+WinsockInitializer WinsockInitializer::singleton;
+#endif
+
+//
+// SocketBase
+//
+
+bool SocketBase::Create() {
+  assert(!valid());
+  socket_ = ::socket(AF_INET, SOCK_STREAM, 0);
+  return valid();
+}
+
+void SocketBase::Close() {
+  if (socket_ != INVALID_SOCKET) {
+    closesocket(socket_);
+    socket_ = INVALID_SOCKET;
+  }
+}
+
+//
+// DataSocket
+//
+
+std::string DataSocket::request_arguments() const {
+  size_t args = request_path_.find('?');
+  if (args != std::string::npos)
+    return request_path_.substr(args + 1);
+  return "";
+}
+
+bool DataSocket::PathEquals(const char* path) const {
+  assert(path);
+  size_t args = request_path_.find('?');
+  if (args != std::string::npos)
+    return request_path_.substr(0, args).compare(path) == 0;
+  return request_path_.compare(path) == 0;
+}
+
+bool DataSocket::OnDataAvailable(bool* close_socket) {
+  assert(valid());
+  char buffer[0xfff] = {0};
+  int bytes = recv(socket_, buffer, sizeof(buffer), 0);
+  if (bytes == SOCKET_ERROR || bytes == 0) {
+    *close_socket = true;
+    return false;
+  }
+
+  *close_socket = false;
+
+  bool ret = true;
+  if (headers_received()) {
+    if (method_ != POST) {
+      // unexpectedly received data.
+      ret = false;
+    } else {
+      data_.append(buffer, bytes);
+    }
+  } else {
+    request_headers_.append(buffer, bytes);
+    size_t found = request_headers_.find(kHeaderTerminator);
+    if (found != std::string::npos) {
+      data_ = request_headers_.substr(found + kHeaderTerminatorLength);
+      request_headers_.resize(found + kHeaderTerminatorLength);
+      ret = ParseHeaders();
+    }
+  }
+  return ret;
+}
+
+bool DataSocket::Send(const std::string& data) const {
+  return send(socket_, data.data(), data.length(), 0) != SOCKET_ERROR;
+}
+
+bool DataSocket::Send(const std::string& status, bool connection_close,
+                      const std::string& content_type,
+                      const std::string& extra_headers,
+                      const std::string& data) const {
+  assert(valid());
+  assert(!status.empty());
+  std::string buffer("HTTP/1.1 " + status + "\r\n");
+
+  buffer += "Server: PeerConnectionTestServer/0.1\r\n"
+            "Cache-Control: no-cache\r\n";
+
+  if (connection_close)
+    buffer += "Connection: close\r\n";
+
+  if (!content_type.empty())
+    buffer += "Content-Type: " + content_type + "\r\n";
+
+  buffer += "Content-Length: " + int2str(data.size()) + "\r\n";
+
+  if (!extra_headers.empty()) {
+    buffer += extra_headers;
+    // Extra headers are assumed to have a separator per header.
+  }
+
+  buffer += kCrossOriginAllowHeaders;
+
+  buffer += "\r\n";
+  buffer += data;
+
+  return Send(buffer);
+}
+
+void DataSocket::Clear() {
+  method_ = INVALID;
+  content_length_ = 0;
+  content_type_.clear();
+  request_path_.clear();
+  request_headers_.clear();
+  data_.clear();
+}
+
+bool DataSocket::ParseHeaders() {
+  assert(!request_headers_.empty());
+  assert(method_ == INVALID);
+  size_t i = request_headers_.find("\r\n");
+  if (i == std::string::npos)
+    return false;
+
+  if (!ParseMethodAndPath(request_headers_.data(), i))
+    return false;
+
+  assert(method_ != INVALID);
+  assert(!request_path_.empty());
+
+  if (method_ == POST) {
+    const char* headers = request_headers_.data() + i + 2;
+    size_t len = request_headers_.length() - i - 2;
+    if (!ParseContentLengthAndType(headers, len))
+      return false;
+  }
+
+  return true;
+}
+
+bool DataSocket::ParseMethodAndPath(const char* begin, size_t len) {
+  struct {
+    const char* method_name;
+    size_t method_name_len;
+    RequestMethod id;
+  } supported_methods[] = {
+    { "GET", 3, GET },
+    { "POST", 4, POST },
+    { "OPTIONS", 7, OPTIONS },
+  };
+
+  const char* path = NULL;
+  for (size_t i = 0; i < ARRAYSIZE(supported_methods); ++i) {
+    if (len > supported_methods[i].method_name_len &&
+        isspace(begin[supported_methods[i].method_name_len]) &&
+        strncmp(begin, supported_methods[i].method_name,
+                supported_methods[i].method_name_len) == 0) {
+      method_ = supported_methods[i].id;
+      path = begin + supported_methods[i].method_name_len;
+      break;
+    }
+  }
+
+  const char* end = begin + len;
+  if (!path || path >= end)
+    return false;
+
+  ++path;
+  begin = path;
+  while (!isspace(*path) && path < end)
+    ++path;
+
+  request_path_.assign(begin, path - begin);
+
+  return true;
+}
+
+bool DataSocket::ParseContentLengthAndType(const char* headers, size_t length) {
+  assert(content_length_ == 0);
+  assert(content_type_.empty());
+
+  const char* end = headers + length;
+  while (headers && headers < end) {
+    if (!isspace(headers[0])) {
+      static const char kContentLength[] = "Content-Length:";
+      static const char kContentType[] = "Content-Type:";
+      if ((headers + ARRAYSIZE(kContentLength)) < end &&
+          strncmp(headers, kContentLength,
+                  ARRAYSIZE(kContentLength) - 1) == 0) {
+        headers += ARRAYSIZE(kContentLength) - 1;
+        while (headers[0] == ' ')
+          ++headers;
+        content_length_ = atoi(headers);
+      } else if ((headers + ARRAYSIZE(kContentType)) < end &&
+                 strncmp(headers, kContentType,
+                         ARRAYSIZE(kContentType) - 1) == 0) {
+        headers += ARRAYSIZE(kContentType) - 1;
+        while (headers[0] == ' ')
+          ++headers;
+        const char* type_end = strstr(headers, "\r\n");
+        if (type_end == NULL)
+          type_end = end;
+        content_type_.assign(headers, type_end);
+      }
+    } else {
+      ++headers;
+    }
+    headers = strstr(headers, "\r\n");
+    if (headers)
+      headers += 2;
+  }
+
+  return !content_type_.empty() && content_length_ != 0;
+}
+
+//
+// ListeningSocket
+//
+
+bool ListeningSocket::Listen(unsigned short port) {
+  assert(valid());
+  int enabled = 1;
+  setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR,
+      reinterpret_cast<const char*>(&enabled), sizeof(enabled));
+  struct sockaddr_in addr = {0};
+  addr.sin_family = AF_INET;
+  addr.sin_addr.s_addr = htonl(INADDR_ANY);
+  addr.sin_port = htons(port);
+  if (bind(socket_, reinterpret_cast<const sockaddr*>(&addr),
+           sizeof(addr)) == SOCKET_ERROR) {
+    printf("bind failed\n");
+    return false;
+  }
+  return listen(socket_, 5) != SOCKET_ERROR;
+}
+
+DataSocket* ListeningSocket::Accept() const {
+  assert(valid());
+  struct sockaddr_in addr = {0};
+  socklen_t size = sizeof(addr);
+  int client = accept(socket_, reinterpret_cast<sockaddr*>(&addr), &size);
+  if (client == INVALID_SOCKET)
+    return NULL;
+
+  return new DataSocket(client);
+}
+
diff --git a/talk/examples/peerconnection/server/data_socket.h b/talk/examples/peerconnection/server/data_socket.h
new file mode 100644
index 0000000..b2ba28d
--- /dev/null
+++ b/talk/examples/peerconnection/server/data_socket.h
@@ -0,0 +1,168 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
+#define TALK_EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
+#pragma once
+
+#ifdef WIN32
+#include <winsock2.h>
+typedef int socklen_t;
+#else
+#include <netinet/in.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#define closesocket close
+#endif
+
+#include <string>
+
+#ifndef SOCKET_ERROR
+#define SOCKET_ERROR (-1)
+#endif
+
+#ifndef INVALID_SOCKET
+#define INVALID_SOCKET  static_cast<int>(~0)
+#endif
+
+class SocketBase {
+ public:
+  SocketBase() : socket_(INVALID_SOCKET) { }
+  explicit SocketBase(int socket) : socket_(socket) { }
+  ~SocketBase() { Close(); }
+
+  int socket() const { return socket_; }
+  bool valid() const { return socket_ != INVALID_SOCKET; }
+
+  bool Create();
+  void Close();
+
+ protected:
+  int socket_;
+};
+
+// Represents an HTTP server socket.
+class DataSocket : public SocketBase {
+ public:
+  enum RequestMethod {
+    INVALID,
+    GET,
+    POST,
+    OPTIONS,
+  };
+
+  explicit DataSocket(int socket)
+      : SocketBase(socket),
+        method_(INVALID),
+        content_length_(0) {
+  }
+
+  ~DataSocket() {
+  }
+
+  static const char kCrossOriginAllowHeaders[];
+
+  bool headers_received() const { return method_ != INVALID; }
+
+  RequestMethod method() const { return method_; }
+
+  const std::string& request_path() const { return request_path_; }
+  std::string request_arguments() const;
+
+  const std::string& data() const { return data_; }
+
+  const std::string& content_type() const { return content_type_; }
+
+  size_t content_length() const { return content_length_; }
+
+  bool request_received() const {
+    return headers_received() && (method_ != POST || data_received());
+  }
+
+  bool data_received() const {
+    return method_ != POST || data_.length() >= content_length_;
+  }
+
+  // Checks if the request path (minus arguments) matches a given path.
+  bool PathEquals(const char* path) const;
+
+  // Called when we have received some data from clients.
+  // Returns false if an error occurred.
+  bool OnDataAvailable(bool* close_socket);
+
+  // Send a raw buffer of bytes.
+  bool Send(const std::string& data) const;
+
+  // Send an HTTP response.  The |status| should start with a valid HTTP
+  // response code, followed by a string.  E.g. "200 OK".
+  // If |connection_close| is set to true, an extra "Connection: close" HTTP
+  // header will be included.  |content_type| is the mime content type, not
+  // including the "Content-Type: " string.
+  // |extra_headers| should be either empty or a list of headers where each
+  // header terminates with "\r\n".
+  // |data| is the body of the message.  It's length will be specified via
+  // a "Content-Length" header.
+  bool Send(const std::string& status, bool connection_close,
+            const std::string& content_type,
+            const std::string& extra_headers, const std::string& data) const;
+
+  // Clears all held state and prepares the socket for receiving a new request.
+  void Clear();
+
+ protected:
+  // A fairly relaxed HTTP header parser.  Parses the method, path and
+  // content length (POST only) of a request.
+  // Returns true if a valid request was received and no errors occurred.
+  bool ParseHeaders();
+
+  // Figures out whether the request is a GET or POST and what path is
+  // being requested.
+  bool ParseMethodAndPath(const char* begin, size_t len);
+
+  // Determines the length of the body and it's mime type.
+  bool ParseContentLengthAndType(const char* headers, size_t length);
+
+ protected:
+  RequestMethod method_;
+  size_t content_length_;
+  std::string content_type_;
+  std::string request_path_;
+  std::string request_headers_;
+  std::string data_;
+};
+
+// The server socket.  Accepts connections and generates DataSocket instances
+// for each new connection.
+class ListeningSocket : public SocketBase {
+ public:
+  ListeningSocket() {}
+
+  bool Listen(unsigned short port);
+  DataSocket* Accept() const;
+};
+
+#endif  // TALK_EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
diff --git a/talk/examples/peerconnection/server/main.cc b/talk/examples/peerconnection/server/main.cc
new file mode 100644
index 0000000..63a573b
--- /dev/null
+++ b/talk/examples/peerconnection/server/main.cc
@@ -0,0 +1,176 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <vector>
+
+#include "talk/examples/peerconnection/server/data_socket.h"
+#include "talk/examples/peerconnection/server/peer_channel.h"
+#include "talk/examples/peerconnection/server/utils.h"
+
+static const size_t kMaxConnections = (FD_SETSIZE - 2);
+
+void HandleBrowserRequest(DataSocket* ds, bool* quit) {
+  assert(ds && ds->valid());
+  assert(quit);
+
+  const std::string& path = ds->request_path();
+
+  *quit = (path.compare("/quit") == 0);
+
+  if (*quit) {
+    ds->Send("200 OK", true, "text/html", "",
+             "<html><body>Quitting...</body></html>");
+  } else if (ds->method() == DataSocket::OPTIONS) {
+    // We'll get this when a browsers do cross-resource-sharing requests.
+    // The headers to allow cross-origin script support will be set inside
+    // Send.
+    ds->Send("200 OK", true, "", "", "");
+  } else {
+    // Here we could write some useful output back to the browser depending on
+    // the path.
+    printf("Received an invalid request: %s\n", ds->request_path().c_str());
+    ds->Send("500 Sorry", true, "text/html", "",
+             "<html><body>Sorry, not yet implemented</body></html>");
+  }
+}
+
+int main(int argc, char** argv) {
+  // TODO: make configurable.
+  static const unsigned short port = 8888;
+
+  ListeningSocket listener;
+  if (!listener.Create()) {
+    printf("Failed to create server socket\n");
+    return -1;
+  } else if (!listener.Listen(port)) {
+    printf("Failed to listen on server socket\n");
+    return -1;
+  }
+
+  printf("Server listening on port %i\n", port);
+
+  PeerChannel clients;
+  typedef std::vector<DataSocket*> SocketArray;
+  SocketArray sockets;
+  bool quit = false;
+  while (!quit) {
+    fd_set socket_set;
+    FD_ZERO(&socket_set);
+    if (listener.valid())
+      FD_SET(listener.socket(), &socket_set);
+
+    for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i)
+      FD_SET((*i)->socket(), &socket_set);
+
+    struct timeval timeout = { 10, 0 };
+    if (select(FD_SETSIZE, &socket_set, NULL, NULL, &timeout) == SOCKET_ERROR) {
+      printf("select failed\n");
+      break;
+    }
+
+    for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) {
+      DataSocket* s = *i;
+      bool socket_done = true;
+      if (FD_ISSET(s->socket(), &socket_set)) {
+        if (s->OnDataAvailable(&socket_done) && s->request_received()) {
+          ChannelMember* member = clients.Lookup(s);
+          if (member || PeerChannel::IsPeerConnection(s)) {
+            if (!member) {
+              if (s->PathEquals("/sign_in")) {
+                clients.AddMember(s);
+              } else {
+                printf("No member found for: %s\n",
+                    s->request_path().c_str());
+                s->Send("500 Error", true, "text/plain", "",
+                        "Peer most likely gone.");
+              }
+            } else if (member->is_wait_request(s)) {
+              // no need to do anything.
+              socket_done = false;
+            } else {
+              ChannelMember* target = clients.IsTargetedRequest(s);
+              if (target) {
+                member->ForwardRequestToPeer(s, target);
+              } else if (s->PathEquals("/sign_out")) {
+                s->Send("200 OK", true, "text/plain", "", "");
+              } else {
+                printf("Couldn't find target for request: %s\n",
+                    s->request_path().c_str());
+                s->Send("500 Error", true, "text/plain", "",
+                        "Peer most likely gone.");
+              }
+            }
+          } else {
+            HandleBrowserRequest(s, &quit);
+            if (quit) {
+              printf("Quitting...\n");
+              FD_CLR(listener.socket(), &socket_set);
+              listener.Close();
+              clients.CloseAll();
+            }
+          }
+        }
+      } else {
+        socket_done = false;
+      }
+
+      if (socket_done) {
+        printf("Disconnecting socket\n");
+        clients.OnClosing(s);
+        assert(s->valid());  // Close must not have been called yet.
+        FD_CLR(s->socket(), &socket_set);
+        delete (*i);
+        i = sockets.erase(i);
+        if (i == sockets.end())
+          break;
+      }
+    }
+
+    clients.CheckForTimeout();
+
+    if (FD_ISSET(listener.socket(), &socket_set)) {
+      DataSocket* s = listener.Accept();
+      if (sockets.size() >= kMaxConnections) {
+        delete s;  // sorry, that's all we can take.
+        printf("Connection limit reached\n");
+      } else {
+        sockets.push_back(s);
+        printf("New connection...\n");
+      }
+    }
+  }
+
+  for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i)
+    delete (*i);
+  sockets.clear();
+
+  return 0;
+}
diff --git a/talk/examples/peerconnection/server/peer_channel.cc b/talk/examples/peerconnection/server/peer_channel.cc
new file mode 100644
index 0000000..5409941
--- /dev/null
+++ b/talk/examples/peerconnection/server/peer_channel.cc
@@ -0,0 +1,369 @@
+/*
+ * 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/examples/peerconnection/server/peer_channel.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+
+#include "talk/examples/peerconnection/server/data_socket.h"
+#include "talk/examples/peerconnection/server/utils.h"
+
+// Set to the peer id of the originator when messages are being
+// exchanged between peers, but set to the id of the receiving peer
+// itself when notifications are sent from the server about the state
+// of other peers.
+//
+// WORKAROUND: Since support for CORS varies greatly from one browser to the
+// next, we don't use a custom name for our peer-id header (originally it was
+// "X-Peer-Id: ").  Instead, we use a "simple header", "Pragma" which should
+// always be exposed to CORS requests.  There is a special CORS header devoted
+// to exposing proprietary headers (Access-Control-Expose-Headers), however
+// at this point it is not working correctly in some popular browsers.
+static const char kPeerIdHeader[] = "Pragma: ";
+
+static const char* kRequestPaths[] = {
+  "/wait", "/sign_out", "/message",
+};
+
+enum RequestPathIndex {
+  kWait,
+  kSignOut,
+  kMessage,
+};
+
+//
+// ChannelMember
+//
+
+int ChannelMember::s_member_id_ = 0;
+
+ChannelMember::ChannelMember(DataSocket* socket)
+  : waiting_socket_(NULL), id_(++s_member_id_),
+    connected_(true), timestamp_(time(NULL)) {
+  assert(socket);
+  assert(socket->method() == DataSocket::GET);
+  assert(socket->PathEquals("/sign_in"));
+  name_ = socket->request_arguments();  // TODO: urldecode
+  if (!name_.length())
+    name_ = "peer_" + int2str(id_);
+  std::replace(name_.begin(), name_.end(), ',', '_');
+}
+
+ChannelMember::~ChannelMember() {
+}
+
+bool ChannelMember::is_wait_request(DataSocket* ds) const {
+  return ds && ds->PathEquals(kRequestPaths[kWait]);
+}
+
+bool ChannelMember::TimedOut() {
+  return waiting_socket_ == NULL && (time(NULL) - timestamp_) > 30;
+}
+
+std::string ChannelMember::GetPeerIdHeader() const {
+  std::string ret(kPeerIdHeader + int2str(id_) + "\r\n");
+  return ret;
+}
+
+bool ChannelMember::NotifyOfOtherMember(const ChannelMember& other) {
+  assert(&other != this);
+  QueueResponse("200 OK", "text/plain", GetPeerIdHeader(),
+                other.GetEntry());
+  return true;
+}
+
+// Returns a string in the form "name,id\n".
+std::string ChannelMember::GetEntry() const {
+  char entry[1024] = {0};
+  sprintf(entry, "%s,%i,%i\n", name_.c_str(), id_, connected_);  // NOLINT
+  return entry;
+}
+
+void ChannelMember::ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer) {
+  assert(peer);
+  assert(ds);
+
+  std::string extra_headers(GetPeerIdHeader());
+
+  if (peer == this) {
+    ds->Send("200 OK", true, ds->content_type(), extra_headers,
+             ds->data());
+  } else {
+    printf("Client %s sending to %s\n",
+        name_.c_str(), peer->name().c_str());
+    peer->QueueResponse("200 OK", ds->content_type(), extra_headers,
+                        ds->data());
+    ds->Send("200 OK", true, "text/plain", "", "");
+  }
+}
+
+void ChannelMember::OnClosing(DataSocket* ds) {
+  if (ds == waiting_socket_) {
+    waiting_socket_ = NULL;
+    timestamp_ = time(NULL);
+  }
+}
+
+void ChannelMember::QueueResponse(const std::string& status,
+                                  const std::string& content_type,
+                                  const std::string& extra_headers,
+                                  const std::string& data) {
+  if (waiting_socket_) {
+    assert(queue_.size() == 0);
+    assert(waiting_socket_->method() == DataSocket::GET);
+    bool ok = waiting_socket_->Send(status, true, content_type, extra_headers,
+                                    data);
+    if (!ok) {
+      printf("Failed to deliver data to waiting socket\n");
+    }
+    waiting_socket_ = NULL;
+    timestamp_ = time(NULL);
+  } else {
+    QueuedResponse qr;
+    qr.status = status;
+    qr.content_type = content_type;
+    qr.extra_headers = extra_headers;
+    qr.data = data;
+    queue_.push(qr);
+  }
+}
+
+void ChannelMember::SetWaitingSocket(DataSocket* ds) {
+  assert(ds->method() == DataSocket::GET);
+  if (ds && !queue_.empty()) {
+    assert(waiting_socket_ == NULL);
+    const QueuedResponse& response = queue_.back();
+    ds->Send(response.status, true, response.content_type,
+             response.extra_headers, response.data);
+    queue_.pop();
+  } else {
+    waiting_socket_ = ds;
+  }
+}
+
+
+//
+// PeerChannel
+//
+
+// static
+bool PeerChannel::IsPeerConnection(const DataSocket* ds) {
+  assert(ds);
+  return (ds->method() == DataSocket::POST && ds->content_length() > 0) ||
+         (ds->method() == DataSocket::GET && ds->PathEquals("/sign_in"));
+}
+
+ChannelMember* PeerChannel::Lookup(DataSocket* ds) const {
+  assert(ds);
+
+  if (ds->method() != DataSocket::GET && ds->method() != DataSocket::POST)
+    return NULL;
+
+  size_t i = 0;
+  for (; i < ARRAYSIZE(kRequestPaths); ++i) {
+    if (ds->PathEquals(kRequestPaths[i]))
+      break;
+  }
+
+  if (i == ARRAYSIZE(kRequestPaths))
+    return NULL;
+
+  std::string args(ds->request_arguments());
+  static const char kPeerId[] = "peer_id=";
+  size_t found = args.find(kPeerId);
+  if (found == std::string::npos)
+    return NULL;
+
+  int id = atoi(&args[found + ARRAYSIZE(kPeerId) - 1]);
+  Members::const_iterator iter = members_.begin();
+  for (; iter != members_.end(); ++iter) {
+    if (id == (*iter)->id()) {
+      if (i == kWait)
+        (*iter)->SetWaitingSocket(ds);
+      if (i == kSignOut)
+        (*iter)->set_disconnected();
+      return *iter;
+    }
+  }
+
+  return NULL;
+}
+
+ChannelMember* PeerChannel::IsTargetedRequest(const DataSocket* ds) const {
+  assert(ds);
+  // Regardless of GET or POST, we look for the peer_id parameter
+  // only in the request_path.
+  const std::string& path = ds->request_path();
+  size_t args = path.find('?');
+  if (args == std::string::npos)
+    return NULL;
+  size_t found;
+  const char kTargetPeerIdParam[] = "to=";
+  do {
+    found = path.find(kTargetPeerIdParam, args);
+    if (found == std::string::npos)
+      return NULL;
+    if (found == (args + 1) || path[found - 1] == '&') {
+      found += ARRAYSIZE(kTargetPeerIdParam) - 1;
+      break;
+    }
+    args = found + ARRAYSIZE(kTargetPeerIdParam) - 1;
+  } while (true);
+  int id = atoi(&path[found]);
+  Members::const_iterator i = members_.begin();
+  for (; i != members_.end(); ++i) {
+    if ((*i)->id() == id) {
+      return *i;
+    }
+  }
+  return NULL;
+}
+
+bool PeerChannel::AddMember(DataSocket* ds) {
+  assert(IsPeerConnection(ds));
+  ChannelMember* new_guy = new ChannelMember(ds);
+  Members failures;
+  BroadcastChangedState(*new_guy, &failures);
+  HandleDeliveryFailures(&failures);
+  members_.push_back(new_guy);
+
+  printf("New member added (total=%s): %s\n",
+      size_t2str(members_.size()).c_str(), new_guy->name().c_str());
+
+  // Let the newly connected peer know about other members of the channel.
+  std::string content_type;
+  std::string response = BuildResponseForNewMember(*new_guy, &content_type);
+  ds->Send("200 Added", true, content_type, new_guy->GetPeerIdHeader(),
+           response);
+  return true;
+}
+
+void PeerChannel::CloseAll() {
+  Members::const_iterator i = members_.begin();
+  for (; i != members_.end(); ++i) {
+    (*i)->QueueResponse("200 OK", "text/plain", "", "Server shutting down");
+  }
+  DeleteAll();
+}
+
+void PeerChannel::OnClosing(DataSocket* ds) {
+  for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+    ChannelMember* m = (*i);
+    m->OnClosing(ds);
+    if (!m->connected()) {
+      i = members_.erase(i);
+      Members failures;
+      BroadcastChangedState(*m, &failures);
+      HandleDeliveryFailures(&failures);
+      delete m;
+      if (i == members_.end())
+        break;
+    }
+  }
+  printf("Total connected: %s\n", size_t2str(members_.size()).c_str());
+}
+
+void PeerChannel::CheckForTimeout() {
+  for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+    ChannelMember* m = (*i);
+    if (m->TimedOut()) {
+      printf("Timeout: %s\n", m->name().c_str());
+      m->set_disconnected();
+      i = members_.erase(i);
+      Members failures;
+      BroadcastChangedState(*m, &failures);
+      HandleDeliveryFailures(&failures);
+      delete m;
+      if (i == members_.end())
+        break;
+    }
+  }
+}
+
+void PeerChannel::DeleteAll() {
+  for (Members::iterator i = members_.begin(); i != members_.end(); ++i)
+    delete (*i);
+  members_.clear();
+}
+
+void PeerChannel::BroadcastChangedState(const ChannelMember& member,
+                                        Members* delivery_failures) {
+  // This function should be called prior to DataSocket::Close().
+  assert(delivery_failures);
+
+  if (!member.connected()) {
+    printf("Member disconnected: %s\n", member.name().c_str());
+  }
+
+  Members::iterator i = members_.begin();
+  for (; i != members_.end(); ++i) {
+    if (&member != (*i)) {
+      if (!(*i)->NotifyOfOtherMember(member)) {
+        (*i)->set_disconnected();
+        delivery_failures->push_back(*i);
+        i = members_.erase(i);
+        if (i == members_.end())
+          break;
+      }
+    }
+  }
+}
+
+void PeerChannel::HandleDeliveryFailures(Members* failures) {
+  assert(failures);
+
+  while (!failures->empty()) {
+    Members::iterator i = failures->begin();
+    ChannelMember* member = *i;
+    assert(!member->connected());
+    failures->erase(i);
+    BroadcastChangedState(*member, failures);
+    delete member;
+  }
+}
+
+// Builds a simple list of "name,id\n" entries for each member.
+std::string PeerChannel::BuildResponseForNewMember(const ChannelMember& member,
+                                                   std::string* content_type) {
+  assert(content_type);
+
+  *content_type = "text/plain";
+  // The peer itself will always be the first entry.
+  std::string response(member.GetEntry());
+  for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+    if (member.id() != (*i)->id()) {
+      assert((*i)->connected());
+      response += (*i)->GetEntry();
+    }
+  }
+
+  return response;
+}
diff --git a/talk/examples/peerconnection/server/peer_channel.h b/talk/examples/peerconnection/server/peer_channel.h
new file mode 100644
index 0000000..2ecc789
--- /dev/null
+++ b/talk/examples/peerconnection/server/peer_channel.h
@@ -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.
+ */
+
+#ifndef TALK_EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
+#define TALK_EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
+#pragma once
+
+#include <time.h>
+
+#include <queue>
+#include <string>
+#include <vector>
+
+class DataSocket;
+
+// Represents a single peer connected to the server.
+class ChannelMember {
+ public:
+  explicit ChannelMember(DataSocket* socket);
+  ~ChannelMember();
+
+  bool connected() const { return connected_; }
+  int id() const { return id_; }
+  void set_disconnected() { connected_ = false; }
+  bool is_wait_request(DataSocket* ds) const;
+  const std::string& name() const { return name_; }
+
+  bool TimedOut();
+
+  std::string GetPeerIdHeader() const;
+
+  bool NotifyOfOtherMember(const ChannelMember& other);
+
+  // Returns a string in the form "name,id\n".
+  std::string GetEntry() const;
+
+  void ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer);
+
+  void OnClosing(DataSocket* ds);
+
+  void QueueResponse(const std::string& status, const std::string& content_type,
+                     const std::string& extra_headers, const std::string& data);
+
+  void SetWaitingSocket(DataSocket* ds);
+
+ protected:
+  struct QueuedResponse {
+    std::string status, content_type, extra_headers, data;
+  };
+
+  DataSocket* waiting_socket_;
+  int id_;
+  bool connected_;
+  time_t timestamp_;
+  std::string name_;
+  std::queue<QueuedResponse> queue_;
+  static int s_member_id_;
+};
+
+// Manages all currently connected peers.
+class PeerChannel {
+ public:
+  typedef std::vector<ChannelMember*> Members;
+
+  PeerChannel() {
+  }
+
+  ~PeerChannel() {
+    DeleteAll();
+  }
+
+  const Members& members() const { return members_; }
+
+  // Returns true if the request should be treated as a new ChannelMember
+  // request.  Otherwise the request is not peerconnection related.
+  static bool IsPeerConnection(const DataSocket* ds);
+
+  // Finds a connected peer that's associated with the |ds| socket.
+  ChannelMember* Lookup(DataSocket* ds) const;
+
+  // Checks if the request has a "peer_id" parameter and if so, looks up the
+  // peer for which the request is targeted at.
+  ChannelMember* IsTargetedRequest(const DataSocket* ds) const;
+
+  // Adds a new ChannelMember instance to the list of connected peers and
+  // associates it with the socket.
+  bool AddMember(DataSocket* ds);
+
+  // Closes all connections and sends a "shutting down" message to all
+  // connected peers.
+  void CloseAll();
+
+  // Called when a socket was determined to be closing by the peer (or if the
+  // connection went dead).
+  void OnClosing(DataSocket* ds);
+
+  void CheckForTimeout();
+
+ protected:
+  void DeleteAll();
+  void BroadcastChangedState(const ChannelMember& member,
+                             Members* delivery_failures);
+  void HandleDeliveryFailures(Members* failures);
+
+  // Builds a simple list of "name,id\n" entries for each member.
+  std::string BuildResponseForNewMember(const ChannelMember& member,
+                                        std::string* content_type);
+
+ protected:
+  Members members_;
+};
+
+#endif  // TALK_EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
diff --git a/talk/examples/peerconnection/server/server_test.html b/talk/examples/peerconnection/server/server_test.html
new file mode 100644
index 0000000..d7c2266
--- /dev/null
+++ b/talk/examples/peerconnection/server/server_test.html
@@ -0,0 +1,263 @@
+<html>
+<head>
+<title>PeerConnection server test page</title>
+
+<script>
+var request = null;
+var hangingGet = null;
+var localName;
+var server;
+var my_id = -1;
+var other_peers = {};
+var message_counter = 0;
+
+function trace(txt) {
+  var elem = document.getElementById("debug");
+  elem.innerHTML += txt + "<br>";
+}
+
+function handleServerNotification(data) {
+  trace("Server notification: " + data);
+  var parsed = data.split(',');
+  if (parseInt(parsed[2]) != 0)
+    other_peers[parseInt(parsed[1])] = parsed[0];
+}
+
+function handlePeerMessage(peer_id, data) {
+  ++message_counter;
+  var str = "Message from '" + other_peers[peer_id] + "'&nbsp;";
+  str += "<span id='toggle_" + message_counter + "' onclick='toggleMe(this);' ";
+  str += "style='cursor: pointer'>+</span><br>";
+  str += "<blockquote id='msg_" + message_counter + "' style='display:none'>";
+  str += data + "</blockquote>";
+  trace(str);
+  if (document.getElementById("loopback").checked) {
+    if (data.search("OFFER") != -1) {
+      // In loopback mode, replace the ROAP OFFER with ROAP ANSWER.
+      data = data.replace("OFFER", "ANSWER");
+      // Keep only the first crypto line for each m line in the answer.
+      var mlineBegin = data.indexOf("m=", 0);
+      while (mlineBegin != -1) {
+        var cryptoBegin = data.indexOf("a=crypto:", mlineBegin);
+        if (cryptoBegin == -1) {
+          // No more crypto lines.
+          break;
+        }
+        // Skip the first crypto line.
+        cryptoBegin = data.indexOf("a=crypto:", cryptoBegin + 1);
+        // Update the mlineBegin to the the next m line.
+        mlineBegin = data.indexOf("m=", mlineBegin + 1);
+        while (cryptoBegin != -1 && cryptoBegin < mlineBegin) {
+          var cryptoEnd = data.indexOf("\\n", cryptoBegin);
+          var crypto = data.substring(cryptoBegin, cryptoEnd + 2);
+          data = data.replace(crypto, "");
+          // Search for the the next crypto line.
+          cryptoBegin = data.indexOf("a=crypto:", cryptoBegin + 1);
+        }
+      }
+      var lines = data.split("\n");
+      for (var i = 1; i < lines.length; ++i) {
+        // Look for the offererSessionId and use it as answererSessionId
+        if (lines[i].length > 0 && lines[i].search("offererSessionId") != -1) {
+          var answer_session_id =
+              lines[i].replace("offererSessionId", "answererSessionId");
+          answer_session_id += "\n" + lines[i];
+          data = data.replace(lines[i], answer_session_id);
+        }
+      }
+    }
+    sendToPeer(peer_id, data);
+  }
+}
+
+function GetIntHeader(r, name) {
+  var val = r.getResponseHeader(name);
+  return val != null && val.length ? parseInt(val) : -1;
+}
+
+function hangingGetCallback() {
+  try {
+    if (hangingGet.readyState != 4)
+      return;
+    if (hangingGet.status != 200) {
+      trace("server error: " + hangingGet.statusText);
+      disconnect();
+    } else {
+      var peer_id = GetIntHeader(hangingGet, "Pragma");
+      if (peer_id == my_id) {
+        handleServerNotification(hangingGet.responseText);
+      } else {
+        handlePeerMessage(peer_id, hangingGet.responseText);
+      }
+    }
+
+    if (hangingGet) {
+      hangingGet.abort();
+      hangingGet = null;
+    }
+
+    if (my_id != -1)
+      window.setTimeout(startHangingGet, 0);
+  } catch (e) {
+    trace("Hanging get error: " + e.description);
+  }
+}
+
+function startHangingGet() {
+  try {
+    hangingGet = new XMLHttpRequest();
+    hangingGet.onreadystatechange = hangingGetCallback;
+    hangingGet.ontimeout = onHangingGetTimeout;
+    hangingGet.open("GET", server + "/wait?peer_id=" + my_id, true);
+    hangingGet.send();  
+  } catch (e) {
+    trace("error" + e.description);
+  }
+}
+
+function onHangingGetTimeout() {
+  trace("hanging get timeout. issuing again.");
+  hangingGet.abort();
+  hangingGet = null;
+  if (my_id != -1)
+    window.setTimeout(startHangingGet, 0);
+}
+
+function signInCallback() {
+  try {
+    if (request.readyState == 4) {
+      if (request.status == 200) {
+        var peers = request.responseText.split("\n");
+        my_id = parseInt(peers[0].split(',')[1]);
+        trace("My id: " + my_id);
+        for (var i = 1; i < peers.length; ++i) {
+          if (peers[i].length > 0) {
+            trace("Peer " + i + ": " + peers[i]);
+            var parsed = peers[i].split(',');
+            other_peers[parseInt(parsed[1])] = parsed[0];
+          }
+        }
+        startHangingGet();
+        request = null;
+      }
+    }
+  } catch (e) {
+    trace("error: " + e.description);
+  }
+}
+
+function signIn() {
+  try {
+    request = new XMLHttpRequest();
+    request.onreadystatechange = signInCallback;
+    request.open("GET", server + "/sign_in?" + localName, true);
+    request.send();
+  } catch (e) {
+    trace("error: " + e.description);
+  }
+}
+
+function sendToPeer(peer_id, data) {
+  if (my_id == -1) {
+    alert("Not connected");
+    return;
+  }
+  if (peer_id == my_id) {
+    alert("Can't send a message to oneself :)");
+    return;
+  }
+  var r = new XMLHttpRequest();
+  r.open("POST", server + "/message?peer_id=" + my_id + "&to=" + peer_id,
+         false);
+  r.setRequestHeader("Content-Type", "text/plain");
+  r.send(data);
+  r = null;
+}
+
+function connect() {
+  localName = document.getElementById("local").value.toLowerCase();
+  server = document.getElementById("server").value.toLowerCase();
+  if (localName.length == 0) {
+    alert("I need a name please.");
+    document.getElementById("local").focus();
+  } else {
+    document.getElementById("connect").disabled = true;
+    document.getElementById("disconnect").disabled = false;
+    document.getElementById("send").disabled = false;
+    signIn();
+  }
+}
+
+function disconnect() {
+  if (request) {
+    request.abort();
+    request = null;
+  }
+  
+  if (hangingGet) {
+    hangingGet.abort();
+    hangingGet = null;
+  }
+
+  if (my_id != -1) {
+    request = new XMLHttpRequest();
+    request.open("GET", server + "/sign_out?peer_id=" + my_id, false);
+    request.send();
+    request = null;
+    my_id = -1;
+  }
+
+  document.getElementById("connect").disabled = false;
+  document.getElementById("disconnect").disabled = true;
+  document.getElementById("send").disabled = true;
+}
+
+window.onbeforeunload = disconnect;
+
+function send() {
+  var text = document.getElementById("message").value;
+  var peer_id = parseInt(document.getElementById("peer_id").value);
+  if (!text.length || peer_id == 0) {
+    alert("No text supplied or invalid peer id");
+  } else {
+    sendToPeer(peer_id, text);
+  }
+}
+
+function toggleMe(obj) {
+  var id = obj.id.replace("toggle", "msg");
+  var t = document.getElementById(id);
+  if (obj.innerText == "+") {
+    obj.innerText = "-";
+    t.style.display = "block";
+  } else {
+    obj.innerText = "+";
+    t.style.display = "none";
+  }
+}
+
+</script>
+
+</head>
+<body>
+Server: <input type="text" id="server" value="http://localhost:8888" /><br>
+<input type="checkbox" id="loopback" checked="checked"/> Loopback (just send
+received messages right back)<br>
+Your name: <input type="text" id="local" value="my_name"/>
+<button id="connect" onclick="connect();">Connect</button>
+<button disabled="true" id="disconnect"
+        onclick="disconnect();">Disconnect</button>
+<br>
+<table><tr><td>
+Target peer id: <input type="text" id="peer_id" size="3"/></td><td>
+Message: <input type="text" id="message"/></td><td>
+<button disabled="true" id="send" onclick="send();">Send</button>
+</td></tr></table>
+<button onclick="document.getElementById('debug').innerHTML='';">
+Clear log</button>
+
+<pre id="debug">
+</pre>
+<br><hr>
+</body>
+</html>
diff --git a/talk/examples/peerconnection/server/utils.cc b/talk/examples/peerconnection/server/utils.cc
new file mode 100644
index 0000000..25ec10c
--- /dev/null
+++ b/talk/examples/peerconnection/server/utils.cc
@@ -0,0 +1,47 @@
+/*
+ * 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/examples/peerconnection/server/utils.h"
+
+#include <stdio.h>
+
+std::string int2str(int i) {
+  char buffer[11] = {0};
+  sprintf(buffer, "%d", i);  // NOLINT
+  return buffer;
+}
+
+std::string size_t2str(size_t i) {
+  char buffer[32] = {0};
+#ifdef WIN32
+  // %zu isn't supported on Windows.
+  sprintf(buffer, "%Iu", i);  // NOLINT
+#else
+  sprintf(buffer, "%zu", i);  // NOLINT
+#endif
+  return buffer;
+}
diff --git a/talk/examples/peerconnection/server/utils.h b/talk/examples/peerconnection/server/utils.h
new file mode 100644
index 0000000..d05a2c3
--- /dev/null
+++ b/talk/examples/peerconnection/server/utils.h
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_
+#define TALK_EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_
+#pragma once
+
+#ifndef assert
+#ifndef WIN32
+#include <assert.h>
+#else
+#ifndef NDEBUG
+#define assert(expr)  ((void)((expr) ? true : __debugbreak()))
+#else
+#define assert(expr)  ((void)0)
+#endif  // NDEBUG
+#endif  // WIN32
+#endif  // assert
+
+#include <string>
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
+#endif
+
+std::string int2str(int i);
+std::string size_t2str(size_t i);
+
+#endif  // TALK_EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_
diff --git a/talk/examples/plus/libjingleplus.cc b/talk/examples/plus/libjingleplus.cc
new file mode 100644
index 0000000..4d27dfd
--- /dev/null
+++ b/talk/examples/plus/libjingleplus.cc
@@ -0,0 +1,736 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+#include "libjingleplus.h"
+#ifdef WIN32
+#include "talk/base/win32socketserver.h"
+#endif
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/logging.h"
+#include "talk/examples/login/xmppauth.h"
+#include "talk/examples/login/xmppsocket.h"
+#include "talk/examples/login/xmpppump.h"
+#include "presencepushtask.h"
+#include "talk/app/status.h"
+#include "talk/app/message.h"
+#include "rostertask.h"
+#include "talk/app/iqtask.h"
+#include "talk/app/presenceouttask.h"
+#include "talk/app/receivemessagetask.h"
+#include "talk/app/rostersettask.h"
+#include "talk/app/sendmessagetask.h"
+
+enum {
+  MSG_START,
+  
+  // main thread to worker
+  MSG_LOGIN,
+  MSG_DISCONNECT,
+  MSG_SEND_PRESENCE,
+  MSG_SEND_DIRECTED_PRESENCE,
+  MSG_SEND_DIRECTED_MUC_PRESENCE,
+  MSG_SEND_XMPP_MESSAGE,
+  MSG_SEND_XMPP_IQ,
+  MSG_UPDATE_ROSTER_ITEM,
+  MSG_REMOVE_ROSTER_ITEM,
+
+  // worker thread to main thread
+  MSG_STATE_CHANGE,
+  MSG_STATUS_UPDATE,
+  MSG_STATUS_ERROR,
+  MSG_ROSTER_REFRESH_STARTED,
+  MSG_ROSTER_REFRESH_FINISHED,
+  MSG_ROSTER_ITEM_UPDATED,
+  MSG_ROSTER_ITEM_REMOVED,
+  MSG_ROSTER_SUBSCRIBE,
+  MSG_ROSTER_UNSUBSCRIBE,
+  MSG_ROSTER_SUBSCRIBED,
+  MSG_ROSTER_UNSUBSCRIBED,
+  MSG_INCOMING_MESSAGE,
+  MSG_IQ_COMPLETE,
+  MSG_XMPP_INPUT,
+  MSG_XMPP_OUTPUT
+};
+
+class LibjinglePlusWorker : public talk_base::MessageHandler, 
+			    public XmppPumpNotify,
+                            public sigslot::has_slots<> {
+ public:	
+  LibjinglePlusWorker(LibjinglePlus *ljp, LibjinglePlusNotify *notify) : 
+    worker_thread_(NULL), ljp_(ljp), notify_(notify),
+    ppt_(NULL), rmt_(NULL), rt_(NULL), is_test_login_(false) {
+
+    main_thread_.reset(new talk_base::AutoThread());
+#ifdef WIN32
+    ss_.reset(new talk_base::Win32SocketServer(main_thread_.get()));
+    main_thread_->set_socketserver(ss_.get());
+#endif
+
+    pump_.reset(new XmppPump(this));
+
+    pump_->client()->SignalLogInput.connect(this, &LibjinglePlusWorker::OnInputDebug);
+    pump_->client()->SignalLogOutput.connect(this, &LibjinglePlusWorker::OnOutputDebug);
+    //pump_->client()->SignalStateChange.connect(this, &LibjinglePlusWorker::OnStateChange);
+    }
+
+  ~LibjinglePlusWorker() {
+    if (worker_thread_) {
+      worker_thread_->Send(this, MSG_DISCONNECT);
+      delete worker_thread_;
+    }
+  }
+  
+  virtual void OnMessage(talk_base::Message *msg) {
+    switch (msg->message_id) {
+    case MSG_START:
+      LoginW();
+      break;
+    case MSG_DISCONNECT:
+      DisconnectW();
+      break;
+    case MSG_SEND_XMPP_MESSAGE:
+      SendXmppMessageW(static_cast<SendMessageData*>(msg->pdata)->m_);
+      delete msg->pdata;
+      break;
+    case MSG_SEND_XMPP_IQ:
+      SendXmppIqW(static_cast<SendIqData*>(msg->pdata)->to_jid_,
+                  static_cast<SendIqData*>(msg->pdata)->is_get_,
+                  static_cast<SendIqData*>(msg->pdata)->xml_element_);
+      delete msg->pdata;
+      break;
+    case MSG_SEND_PRESENCE:
+      SendPresenceW(static_cast<SendPresenceData*>(msg->pdata)->s_);
+      delete msg->pdata;
+      break;
+    case MSG_SEND_DIRECTED_PRESENCE:
+      SendDirectedPresenceW(static_cast<SendDirectedPresenceData*>(msg->pdata)->j_,
+			    static_cast<SendDirectedPresenceData*>(msg->pdata)->s_);
+      delete msg->pdata;
+      break;
+    case MSG_SEND_DIRECTED_MUC_PRESENCE:
+      SendDirectedMUCPresenceW(static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->j_,
+			       static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->s_,
+			       static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->un_,
+			       static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->ac_,
+			       static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->am_,
+			       static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->role_);
+      delete msg->pdata;
+      break;
+    case MSG_UPDATE_ROSTER_ITEM:
+      UpdateRosterItemW(static_cast<UpdateRosterItemData*>(msg->pdata)->jid_,
+			static_cast<UpdateRosterItemData*>(msg->pdata)->n_,
+			static_cast<UpdateRosterItemData*>(msg->pdata)->g_,
+			static_cast<UpdateRosterItemData*>(msg->pdata)->grt_);
+      delete msg->pdata;
+      break;
+    case MSG_REMOVE_ROSTER_ITEM:
+      RemoveRosterItemW(static_cast<JidData*>(msg->pdata)->jid_);
+      delete msg->pdata;
+      break;
+
+
+
+
+    case MSG_STATUS_UPDATE:
+      OnStatusUpdateW(static_cast<SendPresenceData*>(msg->pdata)->s_);
+      delete msg->pdata;
+      break;
+    case MSG_STATUS_ERROR:
+      OnStatusErrorW(static_cast<StatusErrorData*>(msg->pdata)->stanza_);
+      delete msg->pdata;
+      break;
+    case MSG_STATE_CHANGE:
+      OnStateChangeW(static_cast<StateChangeData*>(msg->pdata)->s_);
+      delete msg->pdata;
+      break;
+    case MSG_ROSTER_REFRESH_STARTED:
+      OnRosterRefreshStartedW();
+      break;
+    case MSG_ROSTER_REFRESH_FINISHED:
+      OnRosterRefreshFinishedW();
+      break;
+    case MSG_ROSTER_ITEM_UPDATED:
+      OnRosterItemUpdatedW(static_cast<RosterItemData*>(msg->pdata)->ri_);
+      delete msg->pdata;
+      break;
+    case MSG_ROSTER_ITEM_REMOVED:
+      OnRosterItemRemovedW(static_cast<RosterItemData*>(msg->pdata)->ri_);
+      delete msg->pdata;
+      break;
+    case MSG_ROSTER_SUBSCRIBE:
+      OnRosterSubscribeW(static_cast<JidData*>(msg->pdata)->jid_);
+      delete msg->pdata;
+      break;
+    case MSG_ROSTER_UNSUBSCRIBE:
+      OnRosterUnsubscribeW(static_cast<JidData*>(msg->pdata)->jid_);
+      delete msg->pdata;
+      break;
+    case MSG_ROSTER_SUBSCRIBED:
+      OnRosterSubscribedW(static_cast<JidData*>(msg->pdata)->jid_);
+      delete msg->pdata;
+      break;
+    case MSG_ROSTER_UNSUBSCRIBED:
+      OnRosterUnsubscribedW(static_cast<JidData*>(msg->pdata)->jid_);
+      delete msg->pdata;
+      break;
+    case MSG_INCOMING_MESSAGE:
+      OnIncomingMessageW(static_cast<XmppMessageData*>(msg->pdata)->m_);
+      delete msg->pdata;
+      break;
+    case MSG_IQ_COMPLETE:
+      OnIqCompleteW(static_cast<IqCompleteData*>(msg->pdata)->success_,
+                    static_cast<IqCompleteData*>(msg->pdata)->stanza_);
+      delete msg->pdata;
+      break;
+    case MSG_XMPP_OUTPUT:
+      OnOutputDebugW(static_cast<StringData*>(msg->pdata)->s_);
+      delete msg->pdata;
+      break;
+    case MSG_XMPP_INPUT:
+      OnInputDebugW(static_cast<StringData*>(msg->pdata)->s_);
+      delete msg->pdata;
+      break;
+    }
+  }
+
+  void Login(const std::string &jid, const std::string &password,
+	     const std::string &machine_address, bool is_test, bool cookie_auth) {
+    is_test_login_ = is_test;
+  
+    xcs_.set_user(jid);
+    if (cookie_auth) {
+	    xcs_.set_auth_cookie(password);
+    } else {
+	    talk_base::InsecureCryptStringImpl pass;
+	    pass.password() = password;
+	    xcs_.set_pass(talk_base::CryptString(pass));
+    }
+    xcs_.set_host(is_test ? "google.com" : "gmail.com");
+    xcs_.set_resource("libjingleplus");
+    xcs_.set_server(talk_base::SocketAddress(machine_address, 5222));
+    xcs_.set_use_tls(!is_test);
+    if (is_test) {
+      xcs_.set_allow_plain(true);
+    }
+
+    worker_thread_ = new talk_base::Thread(&pss_);
+    worker_thread_->Start(); 
+    worker_thread_->Send(this, MSG_START);
+  }
+     
+  void SendXmppMessage(const buzz::XmppMessage &m) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    worker_thread_->Post(this, MSG_SEND_XMPP_MESSAGE, new SendMessageData(m));
+  }
+
+  void SendXmppIq(const buzz::Jid &to_jid, bool is_get,
+                  const buzz::XmlElement *xml_element) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    worker_thread_->Post(this, MSG_SEND_XMPP_IQ,
+                         new SendIqData(to_jid, is_get, xml_element));
+  }
+
+  void SendPresence(const buzz::Status & s) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    worker_thread_->Post(this, MSG_SEND_PRESENCE, new SendPresenceData(s));
+  }
+  
+  void SendDirectedPresence (const buzz::Jid &j, const buzz::Status &s) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    worker_thread_->Post(this, MSG_SEND_DIRECTED_PRESENCE, new SendDirectedPresenceData(j,s));
+  }
+  
+  void SendDirectedMUCPresence(const buzz::Jid &j, const buzz::Status &s,
+			       const std::string &un, const std::string &ac,
+			       const std::string &am, const std::string &role) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    worker_thread_->Post(this, MSG_SEND_DIRECTED_MUC_PRESENCE, new SendDirectedMUCPresenceData(j,s,un,ac,am, role));
+  }
+  
+  void UpdateRosterItem(const buzz::Jid & jid, const std::string & name, 
+			const std::vector<std::string> & groups, buzz::GrType grt) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    worker_thread_->Post(this, MSG_UPDATE_ROSTER_ITEM, new UpdateRosterItemData(jid,name,groups,grt));
+  }
+  
+  void RemoveRosterItemW(const buzz::Jid &jid) {
+    buzz::RosterSetTask *rst = new buzz::RosterSetTask(pump_.get()->client());
+    rst->Remove(jid);
+    rst->Start();
+  }
+
+  void RemoveRosterItem(const buzz::Jid &jid) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    worker_thread_->Post(this, MSG_REMOVE_ROSTER_ITEM, new JidData(jid));
+  }
+  
+  void DoCallbacks() {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    talk_base::Message m;
+    while (main_thread_->Get(&m, 0)) {
+      main_thread_->Dispatch(&m);
+    }
+  }
+
+ private:
+
+  struct UpdateRosterItemData : public talk_base::MessageData {
+    UpdateRosterItemData(const buzz::Jid &jid, const std::string &name, 
+			 const std::vector<std::string> &groups, buzz::GrType grt) :
+      jid_(jid), n_(name), g_(groups), grt_(grt) {}
+    buzz::Jid jid_;
+    std::string n_;
+    std::vector<std::string> g_;
+    buzz::GrType grt_;
+  };
+  
+  void UpdateRosterItemW(const buzz::Jid &jid, const std::string &name,
+			 const std::vector<std::string> &groups, buzz::GrType grt) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    buzz::RosterSetTask *rst = new buzz::RosterSetTask(pump_.get()->client());
+    rst->Update(jid, name, groups, grt);
+    rst->Start();
+  }
+
+  struct StringData : public talk_base::MessageData {
+    StringData(std::string s) : s_(s) {}
+    std::string s_;
+  };
+
+  void OnInputDebugW(const std::string &data) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnXmppInput(data);
+  }
+
+  void OnInputDebug(const char *data, int len) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_XMPP_INPUT, new StringData(std::string(data,len)));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }  
+
+  void OnOutputDebugW(const std::string &data) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnXmppOutput(data);
+  }
+  
+  void OnOutputDebug(const char *data, int len) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_XMPP_OUTPUT, new StringData(std::string(data,len)));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  struct StateChangeData : public talk_base::MessageData {
+    StateChangeData(buzz::XmppEngine::State state) : s_(state) {}
+    buzz::XmppEngine::State s_;
+  };
+  
+  void OnStateChange(buzz::XmppEngine::State state) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    switch (state) {
+    case buzz::XmppEngine::STATE_OPEN:           
+      ppt_ = new buzz::PresencePushTask(pump_.get()->client());
+      ppt_->SignalStatusUpdate.connect(this,
+                                       &LibjinglePlusWorker::OnStatusUpdate);
+      ppt_->SignalStatusError.connect(this,
+                                      &LibjinglePlusWorker::OnStatusError);
+      ppt_->Start();
+
+      rmt_ = new buzz::ReceiveMessageTask(pump_.get()->client(), buzz::XmppEngine::HL_ALL);
+      rmt_->SignalIncomingMessage.connect(this, &LibjinglePlusWorker::OnIncomingMessage);
+      rmt_->Start();
+
+      rt_ = new buzz::RosterTask(pump_.get()->client());
+      rt_->SignalRosterItemUpdated.connect(this, &LibjinglePlusWorker::OnRosterItemUpdated);
+      rt_->SignalRosterItemRemoved.connect(this, &LibjinglePlusWorker::OnRosterItemRemoved);
+      rt_->SignalSubscribe.connect(this, &LibjinglePlusWorker::OnRosterSubscribe);
+      rt_->SignalUnsubscribe.connect(this, &LibjinglePlusWorker::OnRosterUnsubscribe);
+      rt_->SignalSubscribed.connect(this, &LibjinglePlusWorker::OnRosterSubscribed);
+      rt_->SignalUnsubscribed.connect(this, &LibjinglePlusWorker::OnRosterUnsubscribed);
+      rt_->SignalRosterRefreshStarted.connect(this, &LibjinglePlusWorker::OnRosterRefreshStarted);
+      rt_->SignalRosterRefreshFinished.connect(this, &LibjinglePlusWorker::OnRosterRefreshFinished);
+      rt_->Start();
+      rt_->RefreshRosterNow();
+
+      break;
+    }
+    main_thread_->Post(this, MSG_STATE_CHANGE, new StateChangeData(state));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  void OnStateChangeW(buzz::XmppEngine::State state) { 
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnStateChange(state);
+  }
+
+  struct RosterItemData : public talk_base::MessageData {
+    RosterItemData(const buzz::RosterItem &ri) : ri_(ri) {}
+    buzz::RosterItem ri_;
+  };
+
+  void OnRosterItemUpdatedW(const buzz::RosterItem &ri) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnRosterItemUpdated(ri);
+  }
+
+  void OnRosterItemUpdated(const buzz::RosterItem &ri, bool huh) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_ROSTER_ITEM_UPDATED, new RosterItemData(ri));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  void OnRosterItemRemovedW(const buzz::RosterItem &ri) { 
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnRosterItemRemoved(ri);
+  }
+  
+  void OnRosterItemRemoved(const buzz::RosterItem &ri) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_ROSTER_ITEM_REMOVED, new RosterItemData(ri));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  struct JidData : public talk_base::MessageData {
+    JidData(const buzz::Jid& jid) : jid_(jid) {}
+    const buzz::Jid jid_;
+  };
+  
+  void OnRosterSubscribeW(const buzz::Jid& jid) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnRosterSubscribe(jid);
+  }
+
+  void OnRosterSubscribe(const buzz::Jid& jid) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_ROSTER_SUBSCRIBE, new JidData(jid));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  void OnRosterUnsubscribeW(const buzz::Jid &jid) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnRosterUnsubscribe(jid);
+  }
+
+  void OnRosterUnsubscribe(const buzz::Jid &jid) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_ROSTER_UNSUBSCRIBE, new JidData(jid));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+  
+  void OnRosterSubscribedW(const buzz::Jid &jid) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnRosterSubscribed(jid);
+  }
+
+  void OnRosterSubscribed(const buzz::Jid &jid) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_ROSTER_SUBSCRIBED, new JidData(jid));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+  
+  void OnRosterUnsubscribedW(const buzz::Jid &jid) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnRosterUnsubscribed(jid);
+  }
+
+  void OnRosterUnsubscribed(const buzz::Jid &jid) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_ROSTER_UNSUBSCRIBED, new JidData(jid));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  void OnRosterRefreshStartedW() {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnRosterRefreshStarted();
+  }
+  
+  void OnRosterRefreshStarted() {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_ROSTER_REFRESH_STARTED);
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  void OnRosterRefreshFinishedW() {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnRosterRefreshFinished();
+  }
+
+  void OnRosterRefreshFinished() {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_ROSTER_REFRESH_FINISHED);
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+  
+  struct XmppMessageData : talk_base::MessageData {
+    XmppMessageData(const buzz::XmppMessage &m) : m_(m) {}
+    buzz::XmppMessage m_;
+  };
+  
+  void OnIncomingMessageW(const buzz::XmppMessage &msg) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnMessage(msg);
+  }
+
+  void OnIncomingMessage(const buzz::XmppMessage &msg) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_INCOMING_MESSAGE, new XmppMessageData(msg));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  void OnStatusUpdateW (const buzz::Status &status) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnStatusUpdate(status);
+  }
+
+  void OnStatusUpdate (const buzz::Status &status) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_STATUS_UPDATE, new SendPresenceData(status));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  struct StatusErrorData : talk_base::MessageData {
+    StatusErrorData(const buzz::XmlElement &stanza) : stanza_(stanza) {}
+    buzz::XmlElement stanza_;
+  };
+
+  void OnStatusErrorW (const buzz::XmlElement &stanza) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnStatusError(stanza);
+  }
+
+  void OnStatusError (const buzz::XmlElement &stanza) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_STATUS_ERROR, new StatusErrorData(stanza));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  void LoginW() {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    XmppSocket* socket = new XmppSocket(true);
+    pump_->DoLogin(xcs_, socket, is_test_login_ ?  NULL : new XmppAuth());
+    socket->SignalCloseEvent.connect(this,
+        &LibjinglePlusWorker::OnXmppSocketClose);
+  }
+
+  void DisconnectW() {
+    assert(talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    pump_->DoDisconnect();
+  }
+
+  void SendXmppMessageW(const buzz::XmppMessage &m) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    buzz::SendMessageTask * smt = new buzz::SendMessageTask(pump_.get()->client());
+    smt->Send(m);
+    smt->Start();
+  }
+
+  void SendXmppIqW(const buzz::Jid &to_jid, bool is_get,
+                   const buzz::XmlElement *xml_element) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    buzz::IqTask *iq_task = new buzz::IqTask(pump_.get()->client(),
+        is_get, to_jid, const_cast<buzz::XmlElement *>(xml_element));
+    iq_task->SignalDone.connect(this, &LibjinglePlusWorker::OnIqComplete);
+    iq_task->Start();
+  }
+
+ struct IqCompleteData : public talk_base::MessageData {
+   IqCompleteData(bool success, const buzz::XmlElement *stanza) :
+     success_(success), stanza_(*stanza) {}
+   bool success_;
+   buzz::XmlElement stanza_;
+ };
+
+  void OnIqCompleteW(bool success, const buzz::XmlElement& stanza) {
+    assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+    if (notify_)
+      notify_->OnIqDone(success, stanza);
+  }
+
+  void OnIqComplete(bool success, const buzz::XmlElement *stanza) {
+    assert(talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    main_thread_->Post(this, MSG_IQ_COMPLETE,
+       new IqCompleteData(success, stanza));
+    if (notify_)
+      notify_->WakeupMainThread();
+  }
+
+  void SendPresenceW(const buzz::Status & s) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    buzz::PresenceOutTask *pot = new buzz::PresenceOutTask(pump_.get()->client());
+    pot->Send(s);
+    pot->Start();
+  }
+
+
+  void SendDirectedMUCPresenceW(const buzz::Jid & j, const buzz::Status & s, 
+			       const std::string &user_nick, const std::string &api_capability,
+			       const std::string &api_message, const std::string &role) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    buzz::PresenceOutTask *pot = new buzz::PresenceOutTask(pump_.get()->client());
+    pot->SendDirectedMUC(j,s,user_nick,api_capability,api_message, role);
+    pot->Start();
+  }
+  
+  void SendDirectedPresenceW(const buzz::Jid & j, const buzz::Status & s) {
+    assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+    buzz::PresenceOutTask *pot = new buzz::PresenceOutTask(pump_.get()->client());
+    pot->SendDirected(j,s);
+    pot->Start();
+  }
+
+  void OnXmppSocketClose(int error) {
+    notify_->OnSocketClose(error);
+  }
+
+ struct SendMessageData : public talk_base::MessageData {
+   SendMessageData(const buzz::XmppMessage &m) : m_(m) {}
+   buzz::XmppMessage m_;
+  };
+
+ struct SendIqData : public talk_base::MessageData {
+   SendIqData(const buzz::Jid &jid, bool is_get, const buzz::XmlElement *m)
+     : to_jid_(jid), is_get_(is_get), xml_element_(m) {}
+   buzz::Jid to_jid_;
+   bool is_get_;
+   const buzz::XmlElement *xml_element_;
+  };
+
+ struct SendPresenceData : public talk_base::MessageData {
+   SendPresenceData(const buzz::Status &s) : s_(s) {}
+   buzz::Status s_;
+  };  
+
+ struct SendDirectedPresenceData : public talk_base::MessageData {
+   SendDirectedPresenceData(const buzz::Jid &j, const buzz::Status &s) : j_(j), s_(s) {}
+   buzz::Jid j_;
+   buzz::Status s_;
+ };
+
+  struct SendDirectedMUCPresenceData : public talk_base::MessageData {
+    SendDirectedMUCPresenceData(const buzz::Jid &j, const buzz::Status &s,
+				const std::string &un, const std::string &ac,
+				const std::string &am, const std::string &role)
+      : j_(j), s_(s), un_(un), ac_(ac), am_(am), role_(role) {}
+    buzz::Jid j_;
+    buzz::Status s_;
+    std::string un_;
+    std::string ac_;
+    std::string am_;
+    std::string role_;
+  };
+
+  talk_base::scoped_ptr<talk_base::Win32SocketServer> ss_;
+  talk_base::scoped_ptr<talk_base::Thread> main_thread_;
+  talk_base::Thread *worker_thread_;
+
+  LibjinglePlus *ljp_;
+  LibjinglePlusNotify *notify_;
+  buzz::XmppClientSettings xcs_;
+  talk_base::PhysicalSocketServer pss_;
+
+  talk_base::scoped_ptr<XmppPump> pump_;
+  buzz::PresencePushTask * ppt_;
+  buzz::ReceiveMessageTask * rmt_;
+  buzz::RosterTask * rt_;
+
+  bool is_test_login_;
+};
+
+LibjinglePlus::LibjinglePlus(LibjinglePlusNotify *notify)
+{
+  worker_ = new LibjinglePlusWorker(this, notify);
+}
+
+LibjinglePlus::~LibjinglePlus()
+{
+ delete worker_;
+  worker_ = NULL;
+}
+
+void LibjinglePlus::Login(const std::string &jid,
+		          const std::string &password,
+		          const std::string &machine_address,
+			  bool is_test, bool cookie_auth) {
+  worker_->Login(jid, password, machine_address, is_test, cookie_auth);
+}
+
+void LibjinglePlus::SendPresence(const buzz::Status & s) {
+  worker_->SendPresence(s);
+}
+
+void LibjinglePlus::SendDirectedPresence(const buzz::Jid & j, const buzz::Status & s) {
+  worker_->SendDirectedPresence(j,s);
+}
+
+void LibjinglePlus::SendDirectedMUCPresence(const buzz::Jid & j,
+    const buzz::Status & s, const std::string &user_nick,
+    const std::string &api_capability, const std::string &api_message,
+    const std::string &role) {
+  worker_->SendDirectedMUCPresence(j,s,user_nick,api_capability,api_message,
+      role);
+}
+
+void LibjinglePlus::SendXmppMessage(const buzz::XmppMessage & m) {
+  worker_->SendXmppMessage(m);
+}
+
+void LibjinglePlus::SendXmppIq(const buzz::Jid &to_jid, bool is_get,
+                               const buzz::XmlElement *iq_element) {
+  worker_->SendXmppIq(to_jid, is_get, iq_element);
+}
+
+void LibjinglePlus::DoCallbacks() {
+  worker_->DoCallbacks();
+}
diff --git a/talk/examples/plus/libjingleplus.h b/talk/examples/plus/libjingleplus.h
new file mode 100644
index 0000000..a2898f5
--- /dev/null
+++ b/talk/examples/plus/libjingleplus.h
@@ -0,0 +1,154 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// LibjinglePlus is a class that connects to Google Talk, creates
+// some common tasks, and emits signals when things change
+
+#ifndef LIBJINGLEPLUS_H__
+#define LIBJINGLEPLUS_H__
+
+#include "talk/base/basicdefs.h"
+#include "talk/app/rosteritem.h"
+#include "talk/app/message.h"
+#include "talk/app/status.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/base/scoped_ptr.h"
+
+
+class LibjinglePlusWorker;
+
+class LibjinglePlusNotify {
+ public:
+  virtual ~LibjinglePlusNotify() {}
+
+  /* Libjingle+ works on its own thread. It will call WakeupMainThread
+   * when it has something to report. The main thread should then wake up,
+   * and call DoCallbacks on the LibjinglePlus object.
+   *
+   * This function gets called from libjingle+'s worker thread. All other
+   * methods in LibjinglePlusNotify get called from the thread you call
+   * DoCallbacks() on.
+   *
+   * If running on Windows, libjingle+ will use Windows messages to generate
+   * callbacks from the main thread, and you don't need to do anything here.
+   */
+  virtual void WakeupMainThread() = 0;
+
+  /* Connection */
+  /* Called when the connection state changes */
+  virtual void OnStateChange(buzz::XmppEngine::State) = 0;
+
+  /* Called when the socket closes */
+  virtual void OnSocketClose(int error_code) = 0;
+
+  /* Called when XMPP is being sent or received. Used for debugging */
+  virtual void OnXmppOutput(const std::string &output) = 0;
+  virtual void OnXmppInput(const std::string &input) = 0;
+
+  /* Presence */
+  /* Called when someone's Status is updated */
+  virtual void OnStatusUpdate(const buzz::Status &status) = 0;
+
+  /* Called when a status update results in an error */
+  virtual void OnStatusError(const buzz::XmlElement &stanza) = 0;
+
+  /* Called with an IQ return code */
+  virtual void OnIqDone(bool success, const buzz::XmlElement &stanza) = 0;
+
+  /* Message */
+  /* Called when a message comes in. */
+  virtual void OnMessage(const buzz::XmppMessage &message) = 0;
+
+  /* Roster */
+
+  /* Called when we start refreshing the roster */
+  virtual void OnRosterRefreshStarted() = 0;
+  /* Called when we have the entire roster */
+  virtual void OnRosterRefreshFinished() = 0;
+  /* Called when an item on the roster is created or updated */
+  virtual void OnRosterItemUpdated(const buzz::RosterItem &ri) = 0;
+  /* Called when an item on the roster is removed */
+  virtual void OnRosterItemRemoved(const buzz::RosterItem &ri) = 0;
+
+  /* Subscriptions */
+  virtual void OnRosterSubscribe(const buzz::Jid &jid) = 0;
+  virtual void OnRosterUnsubscribe(const buzz::Jid &jid) = 0;
+  virtual void OnRosterSubscribed(const buzz::Jid &jid) = 0;
+  virtual void OnRosterUnsubscribed(const buzz::Jid &jid) = 0;
+
+};
+
+class LibjinglePlus 
+{
+ public:
+  /* Provide the constructor with your interface. */
+  LibjinglePlus(LibjinglePlusNotify *notify);
+  ~LibjinglePlus();
+ 
+  /* Logs in and starts doing stuff 
+   *
+   * If cookie_auth is true, password must be a Gaia SID. Otherwise,
+   * it should be the user's password
+   */
+  void Login(const std::string &username, const std::string &password,
+	     const std::string &machine_address, bool is_test, bool cookie_auth);
+
+  /* Set Presence */
+  void SendPresence(const buzz::Status & s);
+  void SendDirectedPresence(const buzz::Jid & j, const buzz::Status & s);
+  void SendDirectedMUCPresence(const buzz::Jid & j, const buzz::Status & s, 
+		       const std::string &user_nick, const std::string &api_capability,
+		       const std::string &api_message, const std::string &role);
+
+  /* Send Message */
+  void SendXmppMessage(const buzz::XmppMessage & m);
+
+  /* Send IQ */
+  void SendXmppIq(const buzz::Jid &to_jid, bool is_get,
+                  const buzz::XmlElement *iq_element);
+
+  /* Set Roster */
+  void UpdateRosterItem(const buzz::Jid & jid, const std::string & name, 
+			const std::vector<std::string> & groups, buzz::GrType grt);
+  void RemoveRosterItem(const buzz::Jid &jid);
+
+  /* Call this from the thread you want to receive callbacks on. Typically, this will be called
+   * after your WakeupMainThread() notify function is called.
+   *
+   * On Windows, libjingle+ will trigger its callback from the Windows message loop, and
+   * you needn't call this yourself.
+   */
+  void DoCallbacks();
+
+ private:
+  void LoginInternal(const std::string &jid, const std::string &password,
+		     const std::string &machine_address, bool is_test);
+
+  LibjinglePlusWorker *worker_;
+};
+
+#endif  // LIBJINGLE_PLUS_H__
diff --git a/talk/examples/plus/presencepushtask.cc b/talk/examples/plus/presencepushtask.cc
new file mode 100644
index 0000000..9d5ed28
--- /dev/null
+++ b/talk/examples/plus/presencepushtask.cc
@@ -0,0 +1,201 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/stringencode.h"
+#include "presencepushtask.h"
+#include "talk/xmpp/constants.h"
+#include <sstream>
+
+
+namespace buzz {
+
+// string helper functions -----------------------------------------------------
+
+static bool
+IsXmlSpace(int ch) {
+  return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
+}
+
+static bool
+ListContainsToken(const std::string & list, const std::string & token) {
+  size_t i = list.find(token);
+  if (i == std::string::npos || token.empty())
+    return false;
+  bool boundary_before = (i == 0 || IsXmlSpace(list[i - 1]));
+  bool boundary_after = (i == list.length() - token.length() || IsXmlSpace(list[i + token.length()]));
+  return boundary_before && boundary_after;
+}
+
+
+bool
+PresencePushTask::HandleStanza(const XmlElement * stanza) {
+  if (stanza->Name() != QN_PRESENCE)
+    return false;
+  if (stanza->HasAttr(QN_TYPE) && stanza->Attr(QN_TYPE) != STR_UNAVAILABLE) {
+    if (stanza->Attr(QN_TYPE) == STR_ERROR) {
+      // Pass on the error.
+      const XmlElement* error_xml_elem = stanza->FirstNamed(QN_ERROR);
+      if (!error_xml_elem) {
+        return false;
+      }
+      SignalStatusError(*error_xml_elem);
+      return true;
+    }
+  }
+  QueueStanza(stanza);
+  return true;
+}
+
+static bool IsUtf8FirstByte(int c) {
+  return (((c)&0x80)==0) || // is single byte
+    ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte
+}
+
+int
+PresencePushTask::ProcessStart() {
+  const XmlElement * stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+  Status s;
+
+  s.set_jid(Jid(stanza->Attr(QN_FROM)));
+
+  if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) {
+    s.set_available(false);
+    SignalStatusUpdate(s);
+  }
+  else {
+    s.set_available(true);
+    const XmlElement * status = stanza->FirstNamed(QN_STATUS);
+    if (status != NULL) {
+      s.set_status(status->BodyText());
+
+      // Truncate status messages longer than 300 bytes
+      if (s.status().length() > 300) {
+        size_t len = 300;
+
+        // Be careful not to split legal utf-8 chars in half
+        while (!IsUtf8FirstByte(s.status()[len]) && len > 0) {
+          len -= 1;
+        }
+        std::string truncated(s.status(), 0, len);
+        s.set_status(truncated);
+      }
+    }
+
+    const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY);
+    if (priority != NULL) {
+      int pri;
+      if (talk_base::FromString(priority->BodyText(), &pri)) {
+        s.set_priority(pri);
+      }
+    }
+
+    const XmlElement * show = stanza->FirstNamed(QN_SHOW);
+    if (show == NULL || show->FirstChild() == NULL) {
+      s.set_show(Status::SHOW_ONLINE);
+    }
+    else {
+      if (show->BodyText() == "away") {
+        s.set_show(Status::SHOW_AWAY);
+      }
+      else if (show->BodyText() == "xa") {
+        s.set_show(Status::SHOW_XA);
+      }
+      else if (show->BodyText() == "dnd") {
+        s.set_show(Status::SHOW_DND);
+      }
+      else if (show->BodyText() == "chat") {
+        s.set_show(Status::SHOW_CHAT);
+      }
+      else {
+        s.set_show(Status::SHOW_ONLINE);
+      }
+    }
+
+    const XmlElement * caps = stanza->FirstNamed(QN_CAPS_C);
+    if (caps != NULL) {
+      std::string node = caps->Attr(QN_NODE);
+      std::string ver = caps->Attr(QN_VER);
+      std::string exts = caps->Attr(QN_EXT);
+
+      s.set_know_capabilities(true);
+      std::string capability;
+      std::stringstream ss(exts);
+      while (ss >> capability) {
+        s.AddCapability(capability);
+      }
+
+      s->set_caps_node(node);
+      s->set_version(ver);
+    }
+
+    const XmlElement* delay = stanza->FirstNamed(kQnDelayX);
+    if (delay != NULL) {
+      // Ideally we would parse this according to the Psuedo ISO-8601 rules
+      // that are laid out in JEP-0082:
+      // http://www.jabber.org/jeps/jep-0082.html
+      std::string stamp = delay->Attr(kQnStamp);
+      s.set_sent_time(stamp);
+    }
+
+    const XmlElement *nick = stanza->FirstNamed(kQnNickname);
+    if (nick) {
+      std::string user_nick = nick->BodyText();
+      s.set_user_nick(user_nick);
+    }
+
+    const XmlElement *plugin = stanza->FirstNamed(QN_PLUGIN);
+    if (plugin) {
+      const XmlElement *api_cap = plugin->FirstNamed(QN_CAPABILITY);
+      if (api_cap) {
+        const std::string &api_capability = api_cap->BodyText();
+        s.set_api_capability(api_capability);
+      }
+      const XmlElement *api_msg = plugin->FirstNamed(QN_DATA);
+      if (api_msg) {
+        const std::string &api_message = api_msg->BodyText();
+        s.set_api_message(api_message);
+      }
+    }
+
+    const XmlElement* data_x = stanza->FirstNamed(QN_MUC_USER_X);
+    if (data_x != NULL) {
+      const XmlElement* item = data_x->FirstNamed(QN_MUC_USER_ITEM);
+      if (item != NULL) {
+        s.set_muc_role(item->Attr(QN_ROLE));
+      }
+    }
+
+    SignalStatusUpdate(s);
+  }
+
+  return STATE_START;
+}
+
+
+}
diff --git a/talk/examples/plus/presencepushtask.h b/talk/examples/plus/presencepushtask.h
new file mode 100644
index 0000000..b9c8038
--- /dev/null
+++ b/talk/examples/plus/presencepushtask.h
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PRESENCEPUSHTASK_H_
+#define _PRESENCEPUSHTASK_H_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/base/sigslot.h"
+#include "talk/app/status.h"
+
+namespace buzz {
+
+class PresencePushTask : public XmppTask {
+
+public:
+  PresencePushTask(Task * parent) : XmppTask(parent, XmppEngine::HL_TYPE) {}
+  virtual int ProcessStart();
+  sigslot::signal1<const Status &>SignalStatusUpdate;
+  sigslot::signal1<const XmlElement &> SignalStatusError;
+
+protected:
+  virtual bool HandleStanza(const XmlElement * stanza);
+};
+
+  
+}
+
+#endif
diff --git a/talk/examples/plus/rostertask.cc b/talk/examples/plus/rostertask.cc
new file mode 100644
index 0000000..1344d08
--- /dev/null
+++ b/talk/examples/plus/rostertask.cc
@@ -0,0 +1,218 @@
+/*
+ * 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 "rostertask.h"
+#include "talk/xmpp/constants.h"
+#include "talk/base/stream.h"
+
+#undef WIN32
+#ifdef WIN32
+#include "talk/app/win32/offlineroster.h"
+#endif
+
+namespace buzz {
+
+class RosterTask::RosterGetTask : public XmppTask {
+public:
+  RosterGetTask(Task * parent) : XmppTask(parent, XmppEngine::HL_SINGLE),
+    done_(false) {}
+
+  virtual int ProcessStart();
+  virtual int ProcessResponse();
+
+protected:
+  virtual bool HandleStanza(const XmlElement * stanza);
+
+  bool done_;
+};
+
+//==============================================================================
+// RosterTask
+//==============================================================================
+void RosterTask::RefreshRosterNow() {
+  RosterGetTask* get_task = new RosterGetTask(this);
+  ResumeTimeout();
+  get_task->Start();
+}
+
+void RosterTask::TranslateItems(const XmlElement * rosterQueryResult) {
+#if defined(FEATURE_ENABLE_PSTN)
+#ifdef WIN32
+  // We build up a list of contacts which have had information persisted offline.
+  // we'll remove items from this list if we get a buzz::SUBSCRIBE_REMOVE
+  // subscription.  After updating all the items from the server, we'll then
+  // update (and merge) any roster items left in our map of offline items
+  XmlElement *el_local = OfflineRoster::RetrieveOfflineRoster(GetClient()->jid());
+  std::map<buzz::Jid, RosterItem> jid_to_item;
+  if (el_local) {
+    for (XmlElement *el_item = el_local->FirstNamed(QN_ROSTER_ITEM);
+         el_item != NULL;
+         el_item = el_item->NextNamed(QN_ROSTER_ITEM)) {
+      RosterItem roster_item;
+      roster_item.FromXml(el_item);
+
+      jid_to_item[roster_item.jid()] = roster_item;
+    }
+  }
+#endif // WIN32
+#endif // FEATURE_ENABLE_PSTN
+
+  const XmlElement * xml_item;
+  for (xml_item = rosterQueryResult->FirstNamed(QN_ROSTER_ITEM);
+       xml_item != NULL; xml_item = xml_item->NextNamed(QN_ROSTER_ITEM)) {
+    RosterItem roster_item;
+    roster_item.FromXml(xml_item);
+
+    if (roster_item.subscription() == buzz::SUBSCRIBE_REMOVE) {
+      SignalRosterItemRemoved(roster_item);
+
+#if defined(FEATURE_ENABLE_PSTN)
+#ifdef WIN32
+      std::map<buzz::Jid, RosterItem>::iterator it =
+        jid_to_item.find(roster_item.jid());
+
+      if (it != jid_to_item.end())
+        jid_to_item.erase(it);
+#endif
+#endif
+    } else {
+      SignalRosterItemUpdated(roster_item, false);
+    }
+  }
+
+#if defined(FEATURE_ENABLE_PSTN)
+#ifdef WIN32
+  for (std::map<buzz::Jid, RosterItem>::iterator it = jid_to_item.begin();
+       it != jid_to_item.end(); ++it) {
+    SignalRosterItemUpdated(it->second, true);
+  }
+#endif
+#endif
+}
+
+int RosterTask::ProcessStart() {
+  const XmlElement * stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  if (stanza->Name() == QN_IQ) {
+    SuspendTimeout();
+    bool result = (stanza->Attr(QN_TYPE) == STR_RESULT);
+    if (result)
+      SignalRosterRefreshStarted();
+
+    TranslateItems(stanza->FirstNamed(QN_ROSTER_QUERY));
+
+    if (result)
+      SignalRosterRefreshFinished();
+  } else if (stanza->Name() == QN_PRESENCE) {
+    Jid jid(stanza->Attr(QN_FROM));
+    std::string type = stanza->Attr(QN_TYPE);
+    if (type == "subscribe")
+      SignalSubscribe(jid);
+    else if (type == "unsubscribe")
+      SignalUnsubscribe(jid);
+    else if (type == "subscribed")
+      SignalSubscribed(jid);
+    else if (type == "unsubscribed")
+      SignalUnsubscribed(jid);
+  }
+
+  return STATE_START;
+}
+
+bool RosterTask::HandleStanza(const XmlElement * stanza) {
+  if (!MatchRequestIq(stanza, STR_SET, QN_ROSTER_QUERY)) {
+    // Not a roster IQ.  Look for a presence instead
+    if (stanza->Name() != QN_PRESENCE)
+      return false;
+    if (!stanza->HasAttr(QN_TYPE))
+      return false;
+    std::string type = stanza->Attr(QN_TYPE);
+    if (type == "subscribe" || type == "unsubscribe" ||
+        type == "subscribed" || type == "unsubscribed") {
+      QueueStanza(stanza);
+      return true;
+    }
+    return false;
+  }
+
+  // only respect roster push from the server
+  Jid from(stanza->Attr(QN_FROM));
+  if (from != JID_EMPTY &&
+      !from.BareEquals(GetClient()->jid()) &&
+      from != Jid(GetClient()->jid().domain()))
+    return false;
+
+  XmlElement * result = MakeIqResult(stanza);
+  result->AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+  SendStanza(result);
+
+  QueueStanza(stanza);
+  return true;
+}
+
+
+//==============================================================================
+// RosterTask::RosterGetTask
+//==============================================================================
+int RosterTask::RosterGetTask::ProcessStart() {
+  talk_base::scoped_ptr<XmlElement> get(MakeIq(STR_GET, JID_EMPTY, task_id()));
+  get->AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+  get->AddAttr(QN_XMLNS_GR, NS_GR, 1);
+  get->AddAttr(QN_GR_EXT, "2", 1);
+  get->AddAttr(QN_GR_INCLUDE, "all", 1);
+  if (SendStanza(get.get()) != XMPP_RETURN_OK) {
+    return STATE_ERROR;
+  }
+  return STATE_RESPONSE;
+}
+
+int RosterTask::RosterGetTask::ProcessResponse() {
+  if (done_)
+    return STATE_DONE;
+  return STATE_BLOCKED;
+}
+
+bool RosterTask::RosterGetTask::HandleStanza(const XmlElement * stanza) {
+  if (!MatchResponseIq(stanza, JID_EMPTY, task_id()))
+    return false;
+
+  if (stanza->Attr(QN_TYPE) != STR_RESULT)
+    return false;
+
+  // Queue the stanza with the parent so these don't get handled out of order
+  RosterTask* parent = static_cast<RosterTask*>(GetParent());
+  parent->QueueStanza(stanza);
+
+  // Wake ourselves so we can go into the done state
+  done_ = true;
+  Wake();
+  return true;
+}
+
+}
diff --git a/talk/examples/plus/rostertask.h b/talk/examples/plus/rostertask.h
new file mode 100644
index 0000000..beab1f9
--- /dev/null
+++ b/talk/examples/plus/rostertask.h
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PHONE_CLIENT_ROSTERTASK_H_
+#define _PHONE_CLIENT_ROSTERTASK_H_
+
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/app/rosteritem.h"
+#include "talk/base/sigslot.h"
+
+namespace buzz {
+
+class RosterTask : public XmppTask {
+public:
+  RosterTask(Task * parent) :
+    XmppTask(parent, XmppEngine::HL_TYPE) {}
+
+  // Roster items removed or updated.  This can come from a push or a get
+  sigslot::signal2<const RosterItem &, bool> SignalRosterItemUpdated;
+  sigslot::signal1<const RosterItem &> SignalRosterItemRemoved;
+
+  // Subscription messages
+  sigslot::signal1<const Jid &> SignalSubscribe;
+  sigslot::signal1<const Jid &> SignalUnsubscribe;
+  sigslot::signal1<const Jid &> SignalSubscribed;
+  sigslot::signal1<const Jid &> SignalUnsubscribed;
+
+  // Roster get
+  void RefreshRosterNow();
+  sigslot::signal0<> SignalRosterRefreshStarted;
+  sigslot::signal0<> SignalRosterRefreshFinished;
+
+  virtual int ProcessStart();
+
+protected:
+  void TranslateItems(const XmlElement *rosterQueryResult);
+
+  virtual bool HandleStanza(const XmlElement * stanza);
+
+  // Inner class for doing the roster get
+  class RosterGetTask;
+  friend class RosterGetTask;
+};
+
+}
+
+#endif // _PHONE_CLIENT_ROSTERTASK_H_
diff --git a/talk/examples/plus/testutil/libjingleplus_main.cc b/talk/examples/plus/testutil/libjingleplus_main.cc
new file mode 100644
index 0000000..b5a0686
--- /dev/null
+++ b/talk/examples/plus/testutil/libjingleplus_main.cc
@@ -0,0 +1,119 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+#include <string>
+
+#include "talk/base/thread.h"
+#include "talk/libjingle-plus/libjingleplus.h"
+#include "talk/libjingle-plus/testutil/libjingleplus_test_notifier.h"
+
+#if defined(_MSC_VER) && (_MSC_VER < 1400)
+void __cdecl std::_Throw(const std::exception &) {}
+std::_Prhand std::_Raise_handler =0;
+#endif
+
+
+void SetConsoleEcho(bool on) {
+#ifdef WIN32
+  HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+  if ((hIn == INVALID_HANDLE_VALUE) || (hIn == NULL))
+    return;
+
+  DWORD mode;
+  if (!GetConsoleMode(hIn, &mode))
+    return;
+
+  if (on) {
+    mode = mode | ENABLE_ECHO_INPUT;
+  } else {
+    mode = mode & ~ENABLE_ECHO_INPUT;
+  }
+
+  SetConsoleMode(hIn, mode);
+#else
+  if (on)
+    system("stty echo");
+  else
+    system("stty -echo");
+#endif
+}
+
+int main (int argc, char **argv)
+{
+  std::string username;
+  std::string password;
+
+  bool gaia = false;
+  
+  for (int i = 1; i < argc; i++) {
+    if (!strcmp(argv[i], "-gaia"))
+      gaia = true;
+  }
+ 
+  std::cout << "Username: ";
+  std::cin >> username;
+  std::cout << (gaia ? "Gaia cookie: " : "Password: ");
+  SetConsoleEcho(false);
+  std::cin >> password;
+  SetConsoleEcho(true);
+
+  // Create a LibjinglePlus object and give it the notifier interface
+  LibjinglePlus ljp(new Notifier);
+  
+  // Login
+  ljp.Login(username, password, "talk.google.com", false, gaia);
+  
+  buzz::Status s;
+  s.set_available(true);
+  s.set_show(buzz::Status::SHOW_ONLINE);
+  s.set_status("I'm online.");
+  
+  buzz::XmppMessage m;
+  m.set_to(buzz::Jid(username + "@gmail.com"));
+  m.set_body("What's up?");
+  
+  // Typically, you would wait for WakeupMainThread to be called, and then call
+  // DoCallbacks. Because I have nothing else to do on the main thread, I'm just going
+  // to do a few things after 10 seconds and then poll every 2ms.
+  Sleep(10000);
+  //  ljp.DoCallbacks();
+  ljp.SendPresence(s);
+  ljp.SendXmppMessage(m);
+
+#ifdef WIN32  
+  MSG msg;
+  while (GetMessage(&msg, NULL, 0, 0)) {
+    DispatchMessage(&msg);
+  }
+#else
+  for (;;) {
+    ljp.DoCallbacks();
+    Sleep(2);
+  }
+#endif
+}
diff --git a/talk/examples/plus/testutil/libjingleplus_test_notifier.h b/talk/examples/plus/testutil/libjingleplus_test_notifier.h
new file mode 100644
index 0000000..3a1af55
--- /dev/null
+++ b/talk/examples/plus/testutil/libjingleplus_test_notifier.h
@@ -0,0 +1,101 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+#include <string>
+
+#include "talk/libjingle-plus/libjingleplus.h"
+
+class Notifier : virtual public LibjinglePlusNotify {
+  virtual void OnStateChange(buzz::XmppEngine::State state) {
+    std::cout << "State change: " << state << std::endl;
+  }
+
+  virtual void OnSocketClose(int error_code) {
+    std::cout << "Socket close: " << error_code << std::endl;
+  }
+
+  virtual void OnXmppOutput(const std::string &output) {
+    std::cout << ">>>>>>>>" << std::endl << output << std::endl << ">>>>>>>>" << std::endl;
+  }
+  
+  virtual void OnXmppInput(const std::string &input) {
+    std::cout << "<<<<<<<<" << std::endl << input << std::endl << "<<<<<<<<" << std::endl;
+  } 
+
+  
+  virtual void OnStatusUpdate(const buzz::Status &status) {
+    std::string from = status.jid().Str();
+    std::cout  << from  << " - " << status.status() << std::endl;
+  }
+
+  virtual void OnStatusError(const buzz::XmlElement &stanza) {
+  }
+  
+  virtual void OnIqDone(bool success, const buzz::XmlElement &stanza) {
+  }
+
+  virtual void OnMessage(const buzz::XmppMessage &m) {
+    if (m.body() != "")
+      std::cout << m.from().Str() << ": " << m.body() << std::endl;
+  }
+
+  void OnRosterItemUpdated(const buzz::RosterItem &ri) {
+    std::cout << "Roster item: " << ri.jid().Str() << std::endl;
+  }
+  
+  virtual void OnRosterItemRemoved(const buzz::RosterItem &ri) {
+    std::cout << "Roster item removed: " << ri.jid().Str() << std::endl;
+  }
+
+  virtual void OnRosterSubscribe(const buzz::Jid& jid) {
+    std::cout << "Subscribing: " << jid.Str() << std::endl;
+  }
+
+  virtual void OnRosterUnsubscribe(const buzz::Jid &jid) {
+    std::cout << "Unsubscribing: " <<jid.Str() << std::endl;
+  }
+  
+  virtual void OnRosterSubscribed(const buzz::Jid &jid) {
+    std::cout << "Subscribed: " << jid.Str() << std::endl;
+  }
+  
+  virtual void OnRosterUnsubscribed(const buzz::Jid &jid) {
+    std::cout << "Unsubscribed: " << jid.Str() << std::endl;
+  }
+
+  virtual void OnRosterRefreshStarted() {
+    std::cout << "Refreshing roster." << std::endl;
+  }
+  
+  virtual void OnRosterRefreshFinished() {
+    std::cout << "Roster refreshed." << std::endl;
+  }
+
+  virtual void WakeupMainThread() {
+  }
+};
diff --git a/talk/examples/plus/testutil/libjingleplus_unittest.cc b/talk/examples/plus/testutil/libjingleplus_unittest.cc
new file mode 100644
index 0000000..d7f6325
--- /dev/null
+++ b/talk/examples/plus/testutil/libjingleplus_unittest.cc
@@ -0,0 +1,52 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "testing/base/gunit.h"
+#include "talk/libjingle-plus/libjingleplus.h"
+#include "talk/libjingle-plus/testutil/libjingleplus_test_notifier.h"
+
+#if defined(_MSC_VER) && (_MSC_VER < 1400)
+void __cdecl std::_Throw(const std::exception &) {}
+std::_Prhand std::_Raise_handler =0;
+#endif
+
+namespace talk_base {
+
+TEST(LibjingleTest, ConstructDestruct) {
+  for (int i = 0; i < 5; ++i) {
+    LibjinglePlus *libjingleplus = new LibjinglePlus(new Notifier);
+    libjingleplus->Login("eaterleaver0", "Buzzt3st", "talk.google.com", false, false);
+
+    delete libjingleplus;
+  }
+}
+}
+
+int main(int argc, char** argv) {
+  testing::ParseGUnitFlags(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
index bfd82b0..ff073a0 100644
--- a/talk/libjingle.scons
+++ b/talk/libjingle.scons
@@ -26,10 +26,14 @@
              ],
              includedirs = [
                "third_party/gtest/include",
+               "third_party/expat-2.0.1/lib",
+               "third_party/srtp",
                "third_party/gtest",
              ],
              cppdefines = [
                "LIBJINGLE_UNITTEST",
+               "EXPAT_RELATIVE_PATH",
+               "SRTP_RELATIVE_PATH",
              ],
 )
 talk.Library(env, name = "srtp",
@@ -70,19 +74,16 @@
              lin_srcs = [
                "base/latebindingsymboltable.cc",
                "base/linux.cc",
+               "base/linuxfdwalk.c",
+               "base/libdbusglibsymboltable.cc",
                "session/phone/libudevsymboltable.cc",
+               "session/phone/linuxdevicemanager.cc",
                "session/phone/v4llookup.cc",
                "sound/alsasoundsystem.cc",
                "sound/alsasymboltable.cc",
                "sound/linuxsoundsystem.cc",
-               "sound/nullsoundsystem.cc",
-               "sound/nullsoundsystemfactory.cc",
                "sound/pulseaudiosoundsystem.cc",
                "sound/pulseaudiosymboltable.cc",
-               "sound/platformsoundsystem.cc",
-               "sound/platformsoundsystemfactory.cc",
-               "sound/soundsysteminterface.cc",
-               "sound/soundsystemproxy.cc",
              ],
              dependent_target_settings = {
                'lin_libs': [
@@ -100,13 +101,17 @@
                "base/macconversion.cc",
                "base/macsocketserver.cc",
                "base/macutils.cc",
+               "base/macwindowpicker.cc",
+               "base/scoped_autorelease_pool.mm",
                "session/phone/carbonvideorenderer.cc",
-               "session/phone/devicemanager_mac.mm",
+               "session/phone/macdevicemanager.cc",
+               "session/phone/macdevicemanagermm.mm",
              ],
              posix_srcs = [
                "base/unixfilesystem.cc",
                "base/opensslidentity.cc",
                "base/opensslstreamadapter.cc",
+               "base/posix.cc",
                "base/sslidentity.cc",
                "base/sslstreamadapter.cc",
              ],
@@ -127,13 +132,17 @@
                "base/asynctcpsocket.cc",
                "base/asyncudpsocket.cc",
                "base/autodetectproxy.cc",
+               "base/bandwidthsmoother.cc",
                "base/base64.cc",
                "base/basicpacketsocketfactory.cc",
                "base/bytebuffer.cc",
                "base/checks.cc",
                "base/common.cc",
+               "base/cpuid.cc",
+               "base/cpumonitor.cc",
                "base/diskcache.cc",
                "base/event.cc",
+               "base/filelock.cc",
                "base/fileutils.cc",
                "base/firewallsocketserver.cc",
                "base/flags.cc",
@@ -144,18 +153,26 @@
                "base/httpcommon.cc",
                "base/httprequest.cc",
                "base/httpserver.cc",
+               "base/ipaddress.cc",
                "base/logging.cc",
                "base/md5c.c",
                "base/messagehandler.cc",
                "base/messagequeue.cc",
+               "base/multipart.cc",
+               "base/natserver.cc",
+               "base/natsocketfactory.cc",
+               "base/nattypes.cc",
                "base/nethelpers.cc",
                "base/network.cc",
                "base/openssladapter.cc",
+               "base/optionsfile.cc",
                "base/pathutils.cc",
                "base/physicalsocketserver.cc",
                "base/proxydetect.cc",
                "base/proxyinfo.cc",
+               "base/proxyserver.cc",
                "base/ratetracker.cc",
+               "base/sharedexclusivelock.cc",
                "base/signalthread.cc",
                "base/socketadapters.cc",
                "base/socketaddress.cc",
@@ -168,18 +185,27 @@
                "base/stringdigest.cc",
                "base/stringencode.cc",
                "base/stringutils.cc",
+               "base/systeminfo.cc",
                "base/task.cc",
                "base/taskparent.cc",
                "base/taskrunner.cc",
+               "base/testclient.cc",
                "base/thread.cc",
-               "base/time.cc",
+               "base/timeutils.cc",
+               "base/timing.cc",
+               "base/transformadapter.cc",
                "base/urlencode.cc",
+               "base/versionparsing.cc",
+               "base/virtualsocketserver.cc",
                "base/worker.cc",
                "p2p/base/constants.cc",
                "p2p/base/p2ptransport.cc",
                "p2p/base/p2ptransportchannel.cc",
                "p2p/base/parsing.cc",
                "p2p/base/port.cc",
+               "p2p/base/portallocator.cc",
+               "p2p/base/portallocatorsessionproxy.cc",
+               "p2p/base/portproxy.cc",
                "p2p/base/pseudotcp.cc",
                "p2p/base/relayport.cc",
                "p2p/base/relayserver.cc",
@@ -199,6 +225,7 @@
                "p2p/base/transportchannelproxy.cc",
                "p2p/base/udpport.cc",
                "p2p/client/basicportallocator.cc",
+               "p2p/client/connectivitychecker.cc",
                "p2p/client/httpportallocator.cc",
                "p2p/client/socketmonitor.cc",
                "session/tunnel/pseudotcpchannel.cc",
@@ -211,11 +238,14 @@
                "session/phone/codec.cc",
                "session/phone/currentspeakermonitor.cc",
                "session/phone/devicemanager.cc",
+               "session/phone/dummydevicemanager.cc",
                "session/phone/filemediaengine.cc",
                "session/phone/gipsmediaengine.cc",
+               "session/phone/filevideocapturer.cc",
                "session/phone/mediaengine.cc",
                "session/phone/mediamessages.cc",
                "session/phone/mediamonitor.cc",
+               "session/phone/mediarecorder.cc",
                "session/phone/mediasession.cc",
                "session/phone/mediasessionclient.cc",
                "session/phone/rtpdump.cc",
@@ -223,6 +253,18 @@
                "session/phone/rtcpmuxfilter.cc",
                "session/phone/soundclip.cc",
                "session/phone/srtpfilter.cc",
+               "session/phone/ssrcmuxfilter.cc",
+               "session/phone/streamparams.cc",
+               "session/phone/videoadapter.cc",
+               "session/phone/videocapturer.cc",
+               "session/phone/videocommon.cc",
+               "session/phone/videoframe.cc",
+               "sound/nullsoundsystem.cc",
+               "sound/nullsoundsystemfactory.cc",
+               "sound/platformsoundsystem.cc",
+               "sound/platformsoundsystemfactory.cc",
+               "sound/soundsysteminterface.cc",
+               "sound/soundsystemproxy.cc",
                "xmllite/qname.cc",
                "xmllite/xmlbuilder.cc",
                "xmllite/xmlconstants.cc",
@@ -234,11 +276,13 @@
                "xmpp/hangoutpubsubclient.cc",
                "xmpp/iqtask.cc",
                "xmpp/jid.cc",
+               "xmpp/moduleimpl.cc",
                "xmpp/mucroomconfigtask.cc",
+               "xmpp/mucroomhistorytask.cc",
                "xmpp/mucroomlookuptask.cc",
                "xmpp/pubsubclient.cc",
+               "xmpp/pubsub_task.cc",
                "xmpp/pubsubtasks.cc",
-               "xmpp/ratelimitmanager.cc",
                "xmpp/receivetask.cc",
                "xmpp/saslmechanism.cc",
                "xmpp/xmppclient.cc",
@@ -253,24 +297,36 @@
                "third_party/expat-2.0.1/lib",
                "third_party/srtp/include",
                "third_party/srtp/crypto/include",
+               "third_party/openssl/include",
              ],
              win_srcs = [
+               "base/diskcache_win32.cc",
                "base/schanneladapter.cc",
                "base/win32.cc",
+               "base/win32regkey.cc",
                "base/win32filesystem.cc",
                "base/win32securityerrors.cc",
                "base/win32socketserver.cc",
                "base/win32socketinit.cc",
                "base/win32window.cc",
+               "base/win32windowpicker.cc",
                "base/winfirewall.cc",
                "base/winping.cc",
+               "session/phone/win32devicemanager.cc",
                "session/phone/gdivideorenderer.cc",
              ],
              mac_ccflags = [
                "-Wno-deprecated-declarations",
              ],
              extra_srcs = [
+               "base/dbus.cc",
                "base/json.cc",
+               "base/linuxwindowpicker.cc",
+               "base/natserver_main.cc",
+               "base/maccocoasocketserver.mm",
+               "base/maccocoathreadhelper.mm",
+               "xmpp/chatroommoduleimpl.cc",
+               "xmpp/rostermoduleimpl.cc",
              ],
 )
 talk.Library(env, name = "xmpphelp",
@@ -278,6 +334,7 @@
                "jingle",
              ],
              srcs = [
+               "examples/login/jingleinfotask.cc",
                "examples/login/xmppauth.cc",
                "examples/login/xmpppump.cc",
                "examples/login/xmppsocket.cc",
@@ -293,10 +350,14 @@
              ],
              includedirs = [
                "third_party/gtest/include",
+               "third_party/expat-2.0.1/lib",
+               "third_party/srtp",
                "third_party/gtest",
              ],
              cppdefines = [
                "LIBJINGLE_UNITTEST",
+               "EXPAT_RELATIVE_PATH",
+               "SRTP_RELATIVE_PATH",
              ],
 )
 talk.App(env, name = "login",
@@ -340,9 +401,6 @@
            "crypto",
            "ssl",
          ],
-         cppdefines = [
-           "FEATURE_ENABLE_VOICEMAIL",
-         ],
          lin_libs = [
 # :TODO: (kedong) add nexus videorenderer later
 #           "videorenderer",
@@ -351,13 +409,12 @@
            "examples/call/call_main.cc",
            "examples/call/callclient.cc",
            "examples/call/console.cc",
-           "examples/call/discoitemsquerytask.cc",
            "examples/call/friendinvitesendtask.cc",
+           "examples/call/mediaenginefactory.cc",
            "examples/call/mucinviterecvtask.cc",
            "examples/call/mucinvitesendtask.cc",
            "examples/call/presenceouttask.cc",
            "examples/call/presencepushtask.cc",
-           "examples/call/voicemailjidrequester.cc",
          ],
          libs = [
            "jingle",
@@ -386,39 +443,267 @@
          ],
 )
 talk.Unittest(env, name = "base",
-              libs = [
-                "jingle",
+              lin_srcs = [
+                "base/latebindingsymboltable_unittest.cc",
+                "base/linux_unittest.cc",
+                "base/linuxfdwalk_unittest.cc",
+              ],
+              mac_srcs = [
+                "base/macsocketserver_unittest.cc",
+                "base/macutils_unittest.cc",
+                "base/macwindowpicker_unittest.cc",
+              ],
+              posix_srcs = [
+                "base/sslidentity_unittest.cc",
+                "base/sslstreamadapter_unittest.cc",
+              ],
+              cppdefines = [
+                "LIBJINGLE_UNITTEST",
+                "EXPAT_RELATIVE_PATH",
+                "SRTP_RELATIVE_PATH",
               ],
               srcs = [
                 "base/asynchttprequest_unittest.cc",
+                "base/atomicops_unittest.cc",
                 "base/autodetectproxy_unittest.cc",
+                "base/bandwidthsmoother_unittest.cc",
+                "base/base64_unittest.cc",
+                "base/buffer_unittest.cc",
                 "base/bytebuffer_unittest.cc",
+                "base/cpuid_unittest.cc",
+                "base/cpumonitor_unittest.cc",
                 "base/event_unittest.cc",
+                "base/filelock_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/ipaddress_unittest.cc",
                 "base/logging_unittest.cc",
                 "base/messagequeue_unittest.cc",
+                "base/multipart_unittest.cc",
+                "base/nat_unittest.cc",
                 "base/network_unittest.cc",
+                "base/optionsfile_unittest.cc",
+                "base/pathutils_unittest.cc",
+                "base/physicalsocketserver_unittest.cc",
+                "base/proxy_unittest.cc",
+                "base/proxydetect_unittest.cc",
+                "base/ratetracker_unittest.cc",
                 "base/referencecountedsingletonfactory_unittest.cc",
+                "base/rollingaccumulator_unittest.cc",
+                "base/sharedexclusivelock_unittest.cc",
                 "base/signalthread_unittest.cc",
+                "base/socket_unittest.cc",
                 "base/socketaddress_unittest.cc",
                 "base/stream_unittest.cc",
                 "base/stringencode_unittest.cc",
                 "base/stringutils_unittest.cc",
+                "base/systeminfo_unittest.cc",
                 "base/task_unittest.cc",
+                "base/testclient_unittest.cc",
                 "base/thread_unittest.cc",
-                "base/time_unittest.cc",
+                "base/timeutils_unittest.cc",
                 "base/urlencode_unittest.cc",
+                "base/versionparsing_unittest.cc",
+                "base/virtualsocket_unittest.cc",
               ],
               includedirs = [
                 "third_party/gtest/include",
+                "third_party/expat-2.0.1/lib",
+                "third_party/srtp",
+                "third_party/gtest",
+              ],
+              win_srcs = [
+                "base/win32_unittest.cc",
+                "base/win32regkey_unittest.cc",
+                "base/win32socketserver_unittest.cc",
+                "base/win32toolhelp_unittest.cc",
+                "base/win32window_unittest.cc",
+                "base/win32windowpicker_unittest.cc",
+                "base/winfirewall_unittest.cc",
+              ],
+              libs = [
+                "jingle",
+              ],
+              extra_srcs = [
+                "base/dbus_unittest.cc",
+                "base/json_unittest.cc",
+                "base/linuxwindowpicker_unittest.cc",
+              ],
+)
+talk.Unittest(env, name = "p2p",
+              mac_FRAMEWORKS = [
+                "Foundation",
+                "IOKit",
+                "QTKit",
+              ],
+              mac_libs = [
+                "crypto",
+                "ssl",
+              ],
+              cppdefines = [
+                "LIBJINGLE_UNITTEST",
+                "EXPAT_RELATIVE_PATH",
+                "SRTP_RELATIVE_PATH",
+              ],
+              srcs = [
+                "p2p/base/p2ptransportchannel_unittest.cc",
+                "p2p/base/port_unittest.cc",
+                "p2p/base/pseudotcp_unittest.cc",
+                "p2p/base/relayport_unittest.cc",
+                "p2p/base/relayserver_unittest.cc",
+                "p2p/base/session_unittest.cc",
+                "p2p/base/stun_unittest.cc",
+                "p2p/base/stunport_unittest.cc",
+                "p2p/base/stunrequest_unittest.cc",
+                "p2p/base/stunserver_unittest.cc",
+                "p2p/base/transport_unittest.cc",
+                "p2p/client/connectivitychecker_unittest.cc",
+                "p2p/client/portallocator_unittest.cc",
+              ],
+              includedirs = [
+                "third_party/gtest/include",
+                "third_party/expat-2.0.1/lib",
+                "third_party/srtp",
+                "third_party/gtest",
+              ],
+              libs = [
+                "jingle",
+                "expat",
+              ],
+)
+talk.Unittest(env, name = "media",
+              libs = [
+                "jingle",
+                "expat",
+                "srtp",
+              ],
+              srcs = [
+                "session/phone/channel_unittest.cc",
+                "session/phone/channelmanager_unittest.cc",
+                "session/phone/codec_unittest.cc",
+                "session/phone/currentspeakermonitor_unittest.cc",
+                "session/phone/devicemanager_unittest.cc",
+                "session/phone/dummydevicemanager_unittest.cc",
+                "session/phone/filemediaengine_unittest.cc",
+                "session/phone/filevideocapturer_unittest.cc",
+                "session/phone/mediarecorder_unittest.cc",
+                "session/phone/mediamessages_unittest.cc",
+                "session/phone/mediasession_unittest.cc",
+                "session/phone/mediasessionclient_unittest.cc",
+                "session/phone/rtcpmuxfilter_unittest.cc",
+                "session/phone/rtpdump_unittest.cc",
+                "session/phone/rtputils_unittest.cc",
+                "session/phone/srtpfilter_unittest.cc",
+                "session/phone/ssrcmuxfilter_unittest.cc",
+                "session/phone/testutils.cc",
+                "session/phone/videocapturer_unittest.cc",
+                "session/phone/videocommon_unittest.cc",
+              ],
+              includedirs = [
+                "third_party/gtest/include",
+                "third_party/expat-2.0.1/lib",
+                "third_party/srtp",
                 "third_party/gtest",
               ],
               cppdefines = [
                 "LIBJINGLE_UNITTEST",
+                "EXPAT_RELATIVE_PATH",
+                "SRTP_RELATIVE_PATH",
+              ],
+              win_libs = [
+                "winmm.lib",
+                "strmiids",
+              ],
+)
+talk.Unittest(env, name = "sound",
+              libs = [
+                "jingle",
+              ],
+              srcs = [
+                "sound/automaticallychosensoundsystem_unittest.cc",
+              ],
+              mac_libs = [
+                "crypto",
+                "ssl",
+              ],
+              includedirs = [
+                "third_party/gtest/include",
+                "third_party/expat-2.0.1/lib",
+                "third_party/srtp",
+                "third_party/gtest",
+              ],
+              cppdefines = [
+                "LIBJINGLE_UNITTEST",
+                "EXPAT_RELATIVE_PATH",
+                "SRTP_RELATIVE_PATH",
+              ],
+)
+talk.Unittest(env, name = "xmllite",
+              libs = [
+                "jingle",
+                "expat",
+              ],
+              srcs = [
+                "xmllite/qname_unittest.cc",
+                "xmllite/xmlbuilder_unittest.cc",
+                "xmllite/xmlelement_unittest.cc",
+                "xmllite/xmlnsstack_unittest.cc",
+                "xmllite/xmlparser_unittest.cc",
+                "xmllite/xmlprinter_unittest.cc",
+              ],
+              mac_libs = [
+                "crypto",
+                "ssl",
+              ],
+              includedirs = [
+                "third_party/gtest/include",
+                "third_party/expat-2.0.1/lib",
+                "third_party/srtp",
+                "third_party/gtest",
+              ],
+              cppdefines = [
+                "LIBJINGLE_UNITTEST",
+                "EXPAT_RELATIVE_PATH",
+                "SRTP_RELATIVE_PATH",
+              ],
+)
+talk.Unittest(env, name = "xmpp",
+              mac_libs = [
+                "crypto",
+                "ssl",
+              ],
+              cppdefines = [
+                "LIBJINGLE_UNITTEST",
+                "EXPAT_RELATIVE_PATH",
+                "SRTP_RELATIVE_PATH",
+              ],
+              srcs = [
+                "xmpp/hangoutpubsubclient_unittest.cc",
+                "xmpp/jid_unittest.cc",
+                "xmpp/mucroomconfigtask_unittest.cc",
+                "xmpp/mucroomlookuptask_unittest.cc",
+                "xmpp/pubsubclient_unittest.cc",
+                "xmpp/pubsubtasks_unittest.cc",
+                "xmpp/util_unittest.cc",
+                "xmpp/xmppengine_unittest.cc",
+                "xmpp/xmpplogintask_unittest.cc",
+                "xmpp/xmppstanzaparser_unittest.cc",
+              ],
+              includedirs = [
+                "third_party/gtest/include",
+                "third_party/expat-2.0.1/lib",
+                "third_party/srtp",
+                "third_party/gtest",
+              ],
+              libs = [
+                "jingle",
+                "expat",
+              ],
+              extra_srcs = [
+                "xmpp/chatroommodule_unittest.cc",
               ],
 )
diff --git a/talk/main.scons b/talk/main.scons
index 5ad5065..6b6b891 100644
--- a/talk/main.scons
+++ b/talk/main.scons
@@ -69,6 +69,8 @@
     'HAVE_SRTP',
     'HAVE_GIPS',
   ],
+  # Ensure the os environment is captured for any scripts we call out to
+  ENV = os.environ,
 )
 
 # This is where we set common environments
@@ -78,18 +80,15 @@
 if platform.architecture()[0] == "64bit":
   root_env.SetBits('build_platform_64bit')
 
-# This bit denotes that an env is for pure 64-bit builds. When set, all build
-# artifacts will be 64-bit. When unset, we will still produce some 64-bit
-# artifacts on 64-bit Linux via the legacy also64bit setting on our targets.
-# When the pure 64-bit build is considered stable, the also64bit setting will go
-# away.
+# This bit denotes that an env is for 64-bit builds. When set, all build
+# artifacts will be 64-bit. When unset, all build artifacts will be 32-bit.
 DeclareBit('host_platform_64bit', 'Platform of the host machine (where artifacts will execute) is 64-bit')
 
 DeclareBit('host_platform_mipsel', 'Platform of the host machine (where artifacts will execute) is mipsel')
 
-def same_arch(env):
-  """Whether the host machine and build machine architectures match."""
-  return env.Bit('host_platform_64bit') == env.Bit('build_platform_64bit')
+def CrossBuilding(env):
+  return env.Bit('host_platform_64bit') != env.Bit('build_platform_64bit')
+root_env.AddMethod(CrossBuilding)
 
 DeclareBit('use_static_openssl', 'Build OpenSSL as a static library')
 
@@ -107,6 +106,8 @@
     'no', 'or', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv',
     'ta', 'te', 'th', 'tl', 'tr', 'uk', 'ur', 'vi', 'zh-CN', 'zh-TW'])
 
+AddTargetGroup('all_breakpads', 'breakpad files can be built')
+
 #-------------------------------------------------------------------------------
 # W I N D O W S
 #
@@ -293,6 +294,13 @@
     # Needed for a clean ABI and for link-time dead-code removal to work
     # properly.
     '-fvisibility=hidden',
+    # Generate debugging info in the DWARF2 format.
+    '-gdwarf-2',
+    # Generate maximal debugging information. (It is stripped from what we ship
+    # to users, so we want it for both dbg and opt.)
+    # Note that hammer automatically supplies "-g" for mac/linux dbg, so that
+    # flag must be filtered out of linux_dbg and mac_dbg envs below.
+    '-g3',
   ],
   CXXFLAGS = [
     '-Wno-non-virtual-dtor',
@@ -311,6 +319,9 @@
     #'fill_plist',
   ],
 )
+# Use static OpenSSL on mac so that we can use the latest APIs on all
+# supported mac platforms (10.5+).
+mac_env.SetBits('use_static_openssl')
 mac_env.Append(
   CPPDEFINES = [
     'OSX',
@@ -320,8 +331,6 @@
     '-arch', 'i386',
     '-isysroot', '/Developer/SDKs/MacOSX10.5.sdk',
     '-fasm-blocks',
-    # Generate debugging info in the DWARF2 format.
-    '-gdwarf-2',
   ],
   LINKFLAGS = [
     '-Wl,-search_paths_first',
@@ -334,8 +343,7 @@
     '-mmacosx-version-min=10.5',
     '-isysroot', '/Developer/SDKs/MacOSX10.5.sdk',
     '-m32',
-    '-dead-strip'
-    '-gdwarf-2',
+    '-dead_strip',
   ],
   FRAMEWORKS = [
     'CoreServices',
@@ -360,8 +368,13 @@
 mac_dbg_env.Append(
   CCFLAGS = [
     '-O0',
-  ]
+  ],
+  CPPDEFINES = [
+    'DEBUG=1',
+  ],
 )
+# Remove -g set by hammer, which is not what we want (we have set -g3 above).
+mac_dbg_env.FilterOut(CCFLAGS = ['-g'])
 envs.append(mac_dbg_env)
 
 mac_opt_env = mac_env.Clone(
@@ -377,6 +390,7 @@
     # out.  Maybe it's a different version of gcc?
     '-Wno-unused-variable',
   ],
+  # Hammer automatically specifies -Os for mac opt.
 )
 envs.append(mac_opt_env)
 
@@ -399,11 +413,6 @@
     # Needed for link-time dead-code removal to work properly.
     '-ffunction-sections',
     '-fdata-sections',
-    # Generate debugging info in the DWARF2 format.
-    '-gdwarf-2',
-    # Generate maximal debugging information. (It is stripped from what we ship
-    # to users, so we want it for both dbg and opt.)
-    '-g3',
   ],
   LINKFLAGS = [
     # Enable dead-code removal.
@@ -467,11 +476,26 @@
   # product would end up being broken on any computer with a different version
   # installed. So instead we build it ourself and statically link to it.
   linux_env.SetBits('use_static_openssl')
+
   linux_env.SetBits('host_platform_mipsel')
-  dbg_groups = ['default', 'all']
+
+  groups = ['all']
+  if not linux_env.CrossBuilding():
+    groups = groups + ['all-native']
+    # The native-arch dbg build is the default.
+    dbg_groups = groups + ['default']
+    native_desc = ', native '
+    # No suffix for native modes.
+    type_suffix = ''
+  else:
+    groups = groups + ['all-cross']
+    dbg_groups = groups
+    native_desc = ', cross-built for '
+
   linux_dbg_env = linux_env.Clone(
     BUILD_TYPE = 'dbg' + type_suffix,
-    BUILD_TYPE_DESCRIPTION = 'Linux debug build' + desc_suffix,
+    BUILD_TYPE_DESCRIPTION = 'Linux debug build%s%s' % (native_desc,
+        desc_suffix),
     BUILD_GROUPS = dbg_groups,
     tools = ['target_debug'],
   )
@@ -481,8 +505,9 @@
 
   linux_opt_env = linux_env.Clone(
     BUILD_TYPE = 'opt' + type_suffix,
-    BUILD_TYPE_DESCRIPTION = 'Linux optimized build' + desc_suffix,
-    BUILD_GROUPS = ['all'],
+    BUILD_TYPE_DESCRIPTION = 'Linux optimized build%s%s' % (native_desc,
+        desc_suffix),
+    BUILD_GROUPS = groups,
     tools = ['target_optimized'],
   )
   # Remove -O2 set by hammer, which is not what we want.
@@ -492,6 +517,90 @@
 
 gen_linux_mips(linux_mipsel_env, '', '')
 
+#-------------------------------------------------------------------------------
+# L I N U X -- C R O S S -- B U I L D
+
+# Cross build requires the following tool names be provided by the environment:
+linux_cross_common_env = linux_common_env.Clone(
+  AR = os.environ.get("AR"),
+  AS = os.environ.get("AS"),
+  LD = os.environ.get("LD"),
+  NM = os.environ.get("NM"),
+  RANLIB = os.environ.get("RANLIB"),
+  CC = str(os.environ.get("CC")) +
+    ' --sysroot=' + str(os.environ.get("SYSROOT")),
+  CXX = str(os.environ.get("CXX")) +
+    ' --sysroot=' + str(os.environ.get("SYSROOT")),
+)
+
+# The rest of these paths and flags are optional:
+if os.environ.get("CPPPATH"):
+  linux_cross_common_env.Append(
+    CPPPATH = os.environ.get("CPPPATH").split(':'),
+  )
+if os.environ.get("LIBPATH"):
+  linux_cross_common_env.Append(
+    LIBPATH = os.environ.get("LIBPATH").split(':'),
+  )
+if os.environ.get("CFLAGS"):
+  linux_cross_common_env.Append(
+    CFLAGS = os.environ.get("CFLAGS").split(' '),
+  )
+if os.environ.get("CCFLAGS"):
+  linux_cross_common_env.Append(
+    CCFLAGS = os.environ.get("CCFLAGS").split(' '),
+  )
+if os.environ.get("CXXFLAGS"):
+  linux_cross_common_env.Append(
+    CXXFLAGS = os.environ.get("CXXFLAGS").split(' '),
+  )
+if os.environ.get("LIBFLAGS"):
+  linux_cross_common_env.Append(
+    _LIBFLAGS = os.environ.get("LIBFLAGS").split(' '),
+  )
+
+#-------------------------------------------------------------------------------
+# L I N U X -- C R O S S -- B U I L D -- A R M
+
+linux_cross_arm_env = linux_cross_common_env.Clone()
+linux_cross_arm_env.Append(
+  CPPDEFINES = [
+    'NACL_BUILD_ARCH=arm',
+    'DISABLE_EFFECTS=1',
+  ],
+  CCFLAGS = [
+    '-fPIC',
+  ],
+)
+DeclareBit('arm', 'ARM build')
+linux_cross_arm_env.SetBits('arm')
+
+# Detect NEON support from the -mfpu build flag.
+DeclareBit('arm_neon', 'ARM supporting neon')
+if '-mfpu=neon' in linux_cross_arm_env['CFLAGS'] or \
+   '-mfpu=neon' in linux_cross_arm_env['CCFLAGS'] or \
+   '-mfpu=neon' in linux_cross_arm_env['CXXFLAGS']:
+  print "Building with ARM NEON support."
+  linux_cross_arm_env.SetBits('arm_neon')
+
+
+linux_cross_arm_dbg_env = linux_cross_arm_env.Clone(
+  BUILD_TYPE = 'arm-dbg',
+  BUILD_TYPE_DESCRIPTION = 'Cross-compiled ARM debug build',
+  BUILD_GROUPS = ['arm'],
+  tools = ['target_debug'],
+)
+envs.append(linux_cross_arm_dbg_env)
+
+linux_cross_arm_opt_env = linux_cross_arm_env.Clone(
+  BUILD_TYPE = 'arm-opt',
+  BUILD_TYPE_DESCRIPTION = 'Cross-compiled ARM optimized build',
+  BUILD_GROUPS = ['arm'],
+  tools = ['target_optimized'],
+)
+envs.append(linux_cross_arm_opt_env)
+
+
 
 # TODO(): Clone linux envs for 64bit.  See 'variant' documentation.
 
diff --git a/talk/p2p/base/constants.cc b/talk/p2p/base/constants.cc
index 178bd59..fe89aa7 100644
--- a/talk/p2p/base/constants.cc
+++ b/talk/p2p/base/constants.cc
@@ -25,207 +25,206 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "talk/p2p/base/constants.h"
+
 #include <string>
 
-#include "talk/p2p/base/constants.h"
 #include "talk/xmllite/qname.h"
 
 namespace cricket {
 
-const std::string NS_EMPTY("");
-const std::string NS_JINGLE("urn:xmpp:jingle:1");
-const std::string NS_JINGLE_DRAFT("google:jingle");
-const std::string NS_GINGLE("http://www.google.com/session");
+const char NS_EMPTY[] = "";
+const char NS_JINGLE[] = "urn:xmpp:jingle:1";
+const char NS_JINGLE_DRAFT[] = "google:jingle";
+const char NS_GINGLE[] = "http://www.google.com/session";
 
 // actions (aka <session> or <jingle>)
-const buzz::QName QN_ACTION(true, NS_EMPTY, "action");
-const std::string LN_INITIATOR("initiator");
-const buzz::QName QN_INITIATOR(true, NS_EMPTY, LN_INITIATOR);
-const buzz::QName QN_CREATOR(true, NS_EMPTY, "creator");
+const buzz::StaticQName QN_ACTION = { NS_EMPTY, "action" };
+const char LN_INITIATOR[] = "initiator";
+const buzz::StaticQName QN_INITIATOR = { NS_EMPTY, LN_INITIATOR };
+const buzz::StaticQName QN_CREATOR = { NS_EMPTY, "creator" };
 
-const buzz::QName QN_JINGLE(true, NS_JINGLE, "jingle");
-const buzz::QName QN_JINGLE_CONTENT(true, NS_JINGLE, "content");
-const buzz::QName QN_JINGLE_CONTENT_NAME(true, NS_EMPTY, "name");
-const buzz::QName QN_JINGLE_CONTENT_MEDIA(true, NS_EMPTY, "media");
-const buzz::QName QN_JINGLE_REASON(true, NS_JINGLE, "reason");
-const std::string JINGLE_CONTENT_MEDIA_AUDIO("audio");
-const std::string JINGLE_CONTENT_MEDIA_VIDEO("video");
-const std::string JINGLE_ACTION_SESSION_INITIATE("session-initiate");
-const std::string JINGLE_ACTION_SESSION_INFO("session-info");
-const std::string JINGLE_ACTION_SESSION_ACCEPT("session-accept");
-const std::string JINGLE_ACTION_SESSION_TERMINATE("session-terminate");
-const std::string JINGLE_ACTION_TRANSPORT_INFO("transport-info");
-const std::string JINGLE_ACTION_TRANSPORT_ACCEPT("transport-accept");
-const std::string JINGLE_ACTION_DESCRIPTION_INFO("description-info");
+const buzz::StaticQName QN_JINGLE = { NS_JINGLE, "jingle" };
+const buzz::StaticQName QN_JINGLE_CONTENT = { NS_JINGLE, "content" };
+const buzz::StaticQName QN_JINGLE_CONTENT_NAME = { NS_EMPTY, "name" };
+const buzz::StaticQName QN_JINGLE_CONTENT_MEDIA = { NS_EMPTY, "media" };
+const buzz::StaticQName QN_JINGLE_REASON = { NS_JINGLE, "reason" };
+const char JINGLE_CONTENT_MEDIA_AUDIO[] = "audio";
+const char JINGLE_CONTENT_MEDIA_VIDEO[] = "video";
+const char JINGLE_ACTION_SESSION_INITIATE[] = "session-initiate";
+const char JINGLE_ACTION_SESSION_INFO[] = "session-info";
+const char JINGLE_ACTION_SESSION_ACCEPT[] = "session-accept";
+const char JINGLE_ACTION_SESSION_TERMINATE[] = "session-terminate";
+const char JINGLE_ACTION_TRANSPORT_INFO[] = "transport-info";
+const char JINGLE_ACTION_TRANSPORT_ACCEPT[] = "transport-accept";
+const char JINGLE_ACTION_DESCRIPTION_INFO[] = "description-info";
 
-const buzz::QName QN_GINGLE_SESSION(true, NS_GINGLE, "session");
-const std::string GINGLE_ACTION_INITIATE("initiate");
-const std::string GINGLE_ACTION_INFO("info");
-const std::string GINGLE_ACTION_ACCEPT("accept");
-const std::string GINGLE_ACTION_REJECT("reject");
-const std::string GINGLE_ACTION_TERMINATE("terminate");
-const std::string GINGLE_ACTION_CANDIDATES("candidates");
-const std::string GINGLE_ACTION_UPDATE("update");
+const buzz::StaticQName QN_GINGLE_SESSION = { NS_GINGLE, "session" };
+const char GINGLE_ACTION_INITIATE[] = "initiate";
+const char GINGLE_ACTION_INFO[] = "info";
+const char GINGLE_ACTION_ACCEPT[] = "accept";
+const char GINGLE_ACTION_REJECT[] = "reject";
+const char GINGLE_ACTION_TERMINATE[] = "terminate";
+const char GINGLE_ACTION_CANDIDATES[] = "candidates";
+const char GINGLE_ACTION_UPDATE[] = "update";
 
-const std::string LN_ERROR("error");
-const buzz::QName QN_GINGLE_REDIRECT(true, NS_GINGLE, "redirect");
-const std::string STR_REDIRECT_PREFIX("xmpp:");
+const char LN_ERROR[] = "error";
+const buzz::StaticQName QN_GINGLE_REDIRECT = { NS_GINGLE, "redirect" };
+const char STR_REDIRECT_PREFIX[] = "xmpp:";
 
 // Session Contents (aka Gingle <session><description>
 //                   or Jingle <content><description>)
-const std::string LN_DESCRIPTION("description");
-const std::string LN_PAYLOADTYPE("payload-type");
-const buzz::QName QN_ID(true, NS_EMPTY, "id");
-const buzz::QName QN_SID(true, NS_EMPTY, "sid");
-const buzz::QName QN_NAME(true, NS_EMPTY, "name");
-const buzz::QName QN_CLOCKRATE(true, NS_EMPTY, "clockrate");
-const buzz::QName QN_BITRATE(true, NS_EMPTY, "bitrate");
-const buzz::QName QN_CHANNELS(true, NS_EMPTY, "channels");
-const buzz::QName QN_WIDTH(true, NS_EMPTY, "width");
-const buzz::QName QN_HEIGHT(true, NS_EMPTY, "height");
-const buzz::QName QN_FRAMERATE(true, NS_EMPTY, "framerate");
-const std::string LN_NAME("name");
-const std::string LN_VALUE("value");
-const buzz::QName QN_PAYLOADTYPE_PARAMETER_NAME(true, NS_EMPTY, LN_NAME);
-const buzz::QName QN_PAYLOADTYPE_PARAMETER_VALUE(true, NS_EMPTY, LN_VALUE);
-const std::string PAYLOADTYPE_PARAMETER_BITRATE("bitrate");
-const std::string PAYLOADTYPE_PARAMETER_HEIGHT("height");
-const std::string PAYLOADTYPE_PARAMETER_WIDTH("width");
-const std::string PAYLOADTYPE_PARAMETER_FRAMERATE("framerate");
-const std::string LN_BANDWIDTH("bandwidth");
+const char LN_DESCRIPTION[] = "description";
+const char LN_PAYLOADTYPE[] = "payload-type";
+const buzz::StaticQName QN_ID = { NS_EMPTY, "id" };
+const buzz::StaticQName QN_SID = { NS_EMPTY, "sid" };
+const buzz::StaticQName QN_NAME = { NS_EMPTY, "name" };
+const buzz::StaticQName QN_CLOCKRATE = { NS_EMPTY, "clockrate" };
+const buzz::StaticQName QN_BITRATE = { NS_EMPTY, "bitrate" };
+const buzz::StaticQName QN_CHANNELS = { NS_EMPTY, "channels" };
+const buzz::StaticQName QN_WIDTH = { NS_EMPTY, "width" };
+const buzz::StaticQName QN_HEIGHT = { NS_EMPTY, "height" };
+const buzz::StaticQName QN_FRAMERATE = { NS_EMPTY, "framerate" };
+const char LN_NAME[] = "name";
+const char LN_VALUE[] = "value";
+const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_NAME = { NS_EMPTY, LN_NAME };
+const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_VALUE = { NS_EMPTY, LN_VALUE };
+const char PAYLOADTYPE_PARAMETER_BITRATE[] = "bitrate";
+const char PAYLOADTYPE_PARAMETER_HEIGHT[] = "height";
+const char PAYLOADTYPE_PARAMETER_WIDTH[] = "width";
+const char PAYLOADTYPE_PARAMETER_FRAMERATE[] = "framerate";
+const char LN_BANDWIDTH[] = "bandwidth";
 
-const std::string CN_AUDIO("audio");
-const std::string CN_VIDEO("video");
-const std::string CN_OTHER("main");
+const char CN_AUDIO[] = "audio";
+const char CN_VIDEO[] = "video";
+const char CN_OTHER[] = "main";
+// other SDP related strings
+const char GN_BUNDLE[] = "BUNDLE";
 
-const std::string NS_JINGLE_RTP("urn:xmpp:jingle:apps:rtp:1");
-const buzz::QName QN_JINGLE_RTP_CONTENT(
-    true, NS_JINGLE_RTP, LN_DESCRIPTION);
-const buzz::QName QN_JINGLE_SSRC(true, NS_EMPTY, "ssrc");
-const buzz::QName QN_JINGLE_RTP_PAYLOADTYPE(
-    true, NS_JINGLE_RTP, LN_PAYLOADTYPE);
-const buzz::QName QN_JINGLE_RTP_BANDWIDTH(
-    true, NS_JINGLE_RTP, LN_BANDWIDTH);
-const buzz::QName QN_JINGLE_RTCP_MUX(true, NS_JINGLE_RTP, "rtcp-mux");
-const buzz::QName QN_PARAMETER(true, NS_JINGLE_RTP, "parameter");
+const char NS_JINGLE_RTP[] = "urn:xmpp:jingle:apps:rtp:1";
+const buzz::StaticQName QN_JINGLE_RTP_CONTENT =
+    { NS_JINGLE_RTP, LN_DESCRIPTION };
+const buzz::StaticQName QN_SSRC = { NS_EMPTY, "ssrc" };
+const buzz::StaticQName QN_JINGLE_RTP_PAYLOADTYPE =
+    { NS_JINGLE_RTP, LN_PAYLOADTYPE };
+const buzz::StaticQName QN_JINGLE_RTP_BANDWIDTH =
+    { NS_JINGLE_RTP, LN_BANDWIDTH };
+const buzz::StaticQName QN_JINGLE_RTCP_MUX = { NS_JINGLE_RTP, "rtcp-mux" };
+const buzz::StaticQName QN_PARAMETER = { NS_JINGLE_RTP, "parameter" };
 
-const std::string NS_GINGLE_AUDIO("http://www.google.com/session/phone");
-const buzz::QName QN_GINGLE_AUDIO_CONTENT(
-    true, NS_GINGLE_AUDIO, LN_DESCRIPTION);
-const buzz::QName QN_GINGLE_AUDIO_PAYLOADTYPE(
-    true, NS_GINGLE_AUDIO, LN_PAYLOADTYPE);
-const buzz::QName QN_GINGLE_AUDIO_SRCID(true, NS_GINGLE_AUDIO, "src-id");
-const std::string NS_GINGLE_VIDEO("http://www.google.com/session/video");
-const buzz::QName QN_GINGLE_VIDEO_CONTENT(
-    true, NS_GINGLE_VIDEO, LN_DESCRIPTION);
-const buzz::QName QN_GINGLE_VIDEO_PAYLOADTYPE(
-    true, NS_GINGLE_VIDEO, LN_PAYLOADTYPE);
-const buzz::QName QN_GINGLE_VIDEO_SRCID(true, NS_GINGLE_VIDEO, "src-id");
-const buzz::QName QN_GINGLE_VIDEO_BANDWIDTH(
-    true, NS_GINGLE_VIDEO, LN_BANDWIDTH);
+const char NS_GINGLE_AUDIO[] = "http://www.google.com/session/phone";
+const buzz::StaticQName QN_GINGLE_AUDIO_CONTENT =
+    { NS_GINGLE_AUDIO, LN_DESCRIPTION };
+const buzz::StaticQName QN_GINGLE_AUDIO_PAYLOADTYPE =
+    { NS_GINGLE_AUDIO, LN_PAYLOADTYPE };
+const buzz::StaticQName QN_GINGLE_AUDIO_SRCID = { NS_GINGLE_AUDIO, "src-id" };
+const char NS_GINGLE_VIDEO[] = "http://www.google.com/session/video";
+const buzz::StaticQName QN_GINGLE_VIDEO_CONTENT =
+    { NS_GINGLE_VIDEO, LN_DESCRIPTION };
+const buzz::StaticQName QN_GINGLE_VIDEO_PAYLOADTYPE =
+    { NS_GINGLE_VIDEO, LN_PAYLOADTYPE };
+const buzz::StaticQName QN_GINGLE_VIDEO_SRCID = { NS_GINGLE_VIDEO, "src-id" };
+const buzz::StaticQName QN_GINGLE_VIDEO_BANDWIDTH =
+    { NS_GINGLE_VIDEO, LN_BANDWIDTH };
 
 // Crypto support.
-const buzz::QName QN_ENCRYPTION(true, NS_JINGLE_RTP, "encryption");
-const buzz::QName QN_ENCRYPTION_REQUIRED(true, NS_EMPTY, "required");
-const buzz::QName QN_CRYPTO(true, NS_JINGLE_RTP, "crypto");
-const buzz::QName QN_GINGLE_AUDIO_CRYPTO_USAGE(true, NS_GINGLE_AUDIO, "usage");
-const buzz::QName QN_GINGLE_VIDEO_CRYPTO_USAGE(true, NS_GINGLE_VIDEO, "usage");
-const buzz::QName QN_CRYPTO_SUITE(true, NS_EMPTY, "crypto-suite");
-const buzz::QName QN_CRYPTO_KEY_PARAMS(true, NS_EMPTY, "key-params");
-const buzz::QName QN_CRYPTO_TAG(true, NS_EMPTY, "tag");
-const buzz::QName QN_CRYPTO_SESSION_PARAMS(true, NS_EMPTY, "session-params");
+const buzz::StaticQName QN_ENCRYPTION = { NS_JINGLE_RTP, "encryption" };
+const buzz::StaticQName QN_ENCRYPTION_REQUIRED = { NS_EMPTY, "required" };
+const buzz::StaticQName QN_CRYPTO = { NS_JINGLE_RTP, "crypto" };
+const buzz::StaticQName QN_GINGLE_AUDIO_CRYPTO_USAGE =
+    { NS_GINGLE_AUDIO, "usage" };
+const buzz::StaticQName QN_GINGLE_VIDEO_CRYPTO_USAGE =
+    { NS_GINGLE_VIDEO, "usage" };
+const buzz::StaticQName QN_CRYPTO_SUITE = { NS_EMPTY, "crypto-suite" };
+const buzz::StaticQName QN_CRYPTO_KEY_PARAMS = { NS_EMPTY, "key-params" };
+const buzz::StaticQName QN_CRYPTO_TAG = { NS_EMPTY, "tag" };
+const buzz::StaticQName QN_CRYPTO_SESSION_PARAMS =
+    { NS_EMPTY, "session-params" };
 
 // transports and candidates
-const std::string LN_TRANSPORT("transport");
-const std::string LN_CANDIDATE("candidate");
-const buzz::QName QN_UFRAG(true, cricket::NS_EMPTY, "ufrag");
-const buzz::QName QN_PWD(true, cricket::NS_EMPTY, "pwd");
-const buzz::QName QN_COMPONENT(true, cricket::NS_EMPTY, "component");
-const buzz::QName QN_IP(true, cricket::NS_EMPTY, "ip");
-const buzz::QName QN_PORT(true, cricket::NS_EMPTY, "port");
-const buzz::QName QN_NETWORK(true, cricket::NS_EMPTY, "network");
-const buzz::QName QN_GENERATION(true, cricket::NS_EMPTY, "generation");
-const buzz::QName QN_PRIORITY(true, cricket::NS_EMPTY, "priority");
-const buzz::QName QN_PROTOCOL(true, cricket::NS_EMPTY, "protocol");
-const std::string JINGLE_CANDIDATE_TYPE_PEER_STUN("prflx");
-const std::string JINGLE_CANDIDATE_TYPE_SERVER_STUN("srflx");
-const std::string JINGLE_CANDIDATE_NAME_RTP("1");
-const std::string JINGLE_CANDIDATE_NAME_RTCP("2");
+const char LN_TRANSPORT[] = "transport";
+const char LN_CANDIDATE[] = "candidate";
+const buzz::StaticQName QN_UFRAG = { cricket::NS_EMPTY, "ufrag" };
+const buzz::StaticQName QN_PWD = { cricket::NS_EMPTY, "pwd" };
+const buzz::StaticQName QN_COMPONENT = { cricket::NS_EMPTY, "component" };
+const buzz::StaticQName QN_IP = { cricket::NS_EMPTY, "ip" };
+const buzz::StaticQName QN_PORT = { cricket::NS_EMPTY, "port" };
+const buzz::StaticQName QN_NETWORK = { cricket::NS_EMPTY, "network" };
+const buzz::StaticQName QN_GENERATION = { cricket::NS_EMPTY, "generation" };
+const buzz::StaticQName QN_PRIORITY = { cricket::NS_EMPTY, "priority" };
+const buzz::StaticQName QN_PROTOCOL = { cricket::NS_EMPTY, "protocol" };
+const char JINGLE_CANDIDATE_TYPE_PEER_STUN[] = "prflx";
+const char JINGLE_CANDIDATE_TYPE_SERVER_STUN[] = "srflx";
+const char JINGLE_CANDIDATE_NAME_RTP[] = "1";
+const char JINGLE_CANDIDATE_NAME_RTCP[] = "2";
 
 // TODO Once we are full ICE-UDP compliant, use this namespace.
 // For now, just use the same as NS_GINGLE_P2P.
-// const std::string NS_JINGLE_ICE_UDP("urn:xmpp:jingle:transports:ice-udp:1");
-const std::string NS_GINGLE_P2P("http://www.google.com/transport/p2p");
-const buzz::QName QN_GINGLE_P2P_TRANSPORT(true, NS_GINGLE_P2P, LN_TRANSPORT);
-const buzz::QName QN_GINGLE_P2P_CANDIDATE(true, NS_GINGLE_P2P, LN_CANDIDATE);
-const buzz::QName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME(
-    true, NS_GINGLE_P2P, "unknown-channel-name");
-const buzz::QName QN_GINGLE_CANDIDATE(true, NS_GINGLE, LN_CANDIDATE);
-const buzz::QName QN_ADDRESS(true, cricket::NS_EMPTY, "address");
-const buzz::QName QN_USERNAME(true, cricket::NS_EMPTY, "username");
-const buzz::QName QN_PASSWORD(true, cricket::NS_EMPTY, "password");
-const buzz::QName QN_PREFERENCE(true, cricket::NS_EMPTY, "preference");
-const std::string GINGLE_CANDIDATE_TYPE_STUN("stun");
-const std::string GINGLE_CANDIDATE_NAME_RTP("rtp");
-const std::string GINGLE_CANDIDATE_NAME_RTCP("rtcp");
-const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTP("video_rtp");
-const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTCP("video_rtcp");
+// const char NS_JINGLE_ICE_UDP[] = "urn:xmpp:jingle:transports:ice-udp:1";
+const char NS_GINGLE_P2P[] = "http://www.google.com/transport/p2p";
+const buzz::StaticQName QN_GINGLE_P2P_TRANSPORT =
+    { NS_GINGLE_P2P, LN_TRANSPORT };
+const buzz::StaticQName QN_GINGLE_P2P_CANDIDATE =
+    { NS_GINGLE_P2P, LN_CANDIDATE };
+const buzz::StaticQName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME =
+    { NS_GINGLE_P2P, "unknown-channel-name" };
+const buzz::StaticQName QN_GINGLE_CANDIDATE = { NS_GINGLE, LN_CANDIDATE };
+const buzz::StaticQName QN_ADDRESS = { cricket::NS_EMPTY, "address" };
+const buzz::StaticQName QN_USERNAME = { cricket::NS_EMPTY, "username" };
+const buzz::StaticQName QN_PASSWORD = { cricket::NS_EMPTY, "password" };
+const buzz::StaticQName QN_PREFERENCE = { cricket::NS_EMPTY, "preference" };
+const char GINGLE_CANDIDATE_TYPE_STUN[] = "stun";
+const char GINGLE_CANDIDATE_NAME_RTP[] = "rtp";
+const char GINGLE_CANDIDATE_NAME_RTCP[] = "rtcp";
+const char GINGLE_CANDIDATE_NAME_VIDEO_RTP[] = "video_rtp";
+const char GINGLE_CANDIDATE_NAME_VIDEO_RTCP[] = "video_rtcp";
 
 // terminate reasons and errors
-const std::string JINGLE_ERROR_BAD_REQUEST("bad-request");
-const std::string JINGLE_ERROR_OUT_OF_ORDER("out-of-order");
-const std::string JINGLE_ERROR_UNKNOWN_SESSION("unknown-session");
+const char JINGLE_ERROR_BAD_REQUEST[] = "bad-request";
+const char JINGLE_ERROR_OUT_OF_ORDER[] = "out-of-order";
+const char JINGLE_ERROR_UNKNOWN_SESSION[] = "unknown-session";
 
 // Call terminate reasons from XEP-166
-const std::string STR_TERMINATE_DECLINE("decline");
-const std::string STR_TERMINATE_SUCCESS("success");
-const std::string STR_TERMINATE_ERROR("general-error");
-const std::string STR_TERMINATE_INCOMPATIBLE_PARAMETERS(
-    "incompatible-parameters");
+const char STR_TERMINATE_DECLINE[] = "decline";
+const char STR_TERMINATE_SUCCESS[] = "success";
+const char STR_TERMINATE_ERROR[] = "general-error";
+const char STR_TERMINATE_INCOMPATIBLE_PARAMETERS[] = "incompatible-parameters";
 
 // Old terminate reasons used by cricket
-const std::string STR_TERMINATE_CALL_ENDED("call-ended");
-const std::string STR_TERMINATE_RECIPIENT_UNAVAILABLE("recipient-unavailable");
-const std::string STR_TERMINATE_RECIPIENT_BUSY("recipient-busy");
-const std::string STR_TERMINATE_INSUFFICIENT_FUNDS("insufficient-funds");
-const std::string STR_TERMINATE_NUMBER_MALFORMED("number-malformed");
-const std::string STR_TERMINATE_NUMBER_DISALLOWED("number-disallowed");
-const std::string STR_TERMINATE_PROTOCOL_ERROR("protocol-error");
-const std::string STR_TERMINATE_INTERNAL_SERVER_ERROR("internal-server-error");
-const std::string STR_TERMINATE_UNKNOWN_ERROR("unknown-error");
+const char STR_TERMINATE_CALL_ENDED[] = "call-ended";
+const char STR_TERMINATE_RECIPIENT_UNAVAILABLE[] = "recipient-unavailable";
+const char STR_TERMINATE_RECIPIENT_BUSY[] = "recipient-busy";
+const char STR_TERMINATE_INSUFFICIENT_FUNDS[] = "insufficient-funds";
+const char STR_TERMINATE_NUMBER_MALFORMED[] = "number-malformed";
+const char STR_TERMINATE_NUMBER_DISALLOWED[] = "number-disallowed";
+const char STR_TERMINATE_PROTOCOL_ERROR[] = "protocol-error";
+const char STR_TERMINATE_INTERNAL_SERVER_ERROR[] = "internal-server-error";
+const char STR_TERMINATE_UNKNOWN_ERROR[] = "unknown-error";
 
 // Draft view and notify messages.
-const buzz::QName QN_JINGLE_DRAFT_CONTENT_NAME(true, cricket::NS_EMPTY, "name");
-const std::string STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO("video");
-const std::string STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO("audio");
-const buzz::QName QN_JINGLE_DRAFT_NOTIFY(true, NS_JINGLE_DRAFT, "notify");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE(
-    true, NS_JINGLE_DRAFT, "source");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE_NICK(true, cricket::NS_EMPTY, "nick");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE_NAME(true, cricket::NS_EMPTY, "name");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE_USAGE(true, cricket::NS_EMPTY, "usage");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE_STATE(true, cricket::NS_EMPTY, "state");
-const std::string STR_JINGLE_DRAFT_SOURCE_STATE_REMOVED("removed");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE_SSRC(true, NS_JINGLE_DRAFT, "ssrc");
-const buzz::QName QN_JINGLE_DRAFT_VIEW(true, NS_JINGLE_DRAFT, "view");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_TYPE(true, cricket::NS_EMPTY, "type");
-const std::string STR_JINGLE_DRAFT_VIEW_TYPE_NONE("none");
-const std::string STR_JINGLE_DRAFT_VIEW_TYPE_STATIC("static");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_SSRC(true, cricket::NS_EMPTY, "ssrc");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS(true, NS_JINGLE_DRAFT, "params");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_WIDTH(
-    true, cricket::NS_EMPTY, "width");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_HEIGHT(
-    true, cricket::NS_EMPTY, "height");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_FRAMERATE(
-    true, cricket::NS_EMPTY, "framerate");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_PREFERENCE(
-    true, cricket::NS_EMPTY, "preference");
+const char STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO[] = "video";
+const char STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO[] = "audio";
+const buzz::StaticQName QN_NICK = { cricket::NS_EMPTY, "nick" };
+const buzz::StaticQName QN_TYPE = { cricket::NS_EMPTY, "type" };
+const buzz::StaticQName QN_JINGLE_DRAFT_VIEW = { NS_JINGLE_DRAFT, "view" };
+const char STR_JINGLE_DRAFT_VIEW_TYPE_NONE[] = "none";
+const char STR_JINGLE_DRAFT_VIEW_TYPE_STATIC[] = "static";
+const buzz::StaticQName QN_JINGLE_DRAFT_PARAMS = { NS_JINGLE_DRAFT, "params" };
+const buzz::StaticQName QN_JINGLE_DRAFT_STREAMS = { NS_JINGLE_DRAFT, "streams" };
+const buzz::StaticQName QN_JINGLE_DRAFT_STREAM = { NS_JINGLE_DRAFT, "stream" };
+const buzz::StaticQName QN_DISPLAY = { cricket::NS_EMPTY, "display" };
+const buzz::StaticQName QN_CNAME = { cricket::NS_EMPTY, "cname" };
+const buzz::StaticQName QN_JINGLE_DRAFT_SSRC = { NS_JINGLE_DRAFT, "ssrc" };
+const buzz::StaticQName QN_JINGLE_DRAFT_SSRC_GROUP =
+    { NS_JINGLE_DRAFT, "ssrc-group" };
+const buzz::StaticQName QN_SEMANTICS = { cricket::NS_EMPTY, "semantics" };
+const buzz::StaticQName QN_JINGLE_LEGACY_NOTIFY = { NS_JINGLE_DRAFT, "notify" };
+const buzz::StaticQName QN_JINGLE_LEGACY_SOURCE = { NS_JINGLE_DRAFT, "source" };
 
 // old stuff
 #ifdef FEATURE_ENABLE_VOICEMAIL
-const std::string NS_VOICEMAIL("http://www.google.com/session/voicemail");
-const buzz::QName QN_VOICEMAIL_REGARDING(true, NS_VOICEMAIL, "regarding");
+const char NS_VOICEMAIL[] = "http://www.google.com/session/voicemail";
+const buzz::StaticQName QN_VOICEMAIL_REGARDING = { NS_VOICEMAIL, "regarding" };
 #endif
 
 }  // namespace cricket
diff --git a/talk/p2p/base/constants.h b/talk/p2p/base/constants.h
index ab0ed2c..3e85914 100644
--- a/talk/p2p/base/constants.h
+++ b/talk/p2p/base/constants.h
@@ -42,10 +42,10 @@
 //   these are useful when you need to find a tag
 //   that has different namespaces (like <description> or <transport>)
 
-extern const std::string NS_EMPTY;
-extern const std::string NS_JINGLE;
-extern const std::string NS_JINGLE_DRAFT;
-extern const std::string NS_GINGLE;
+extern const char NS_EMPTY[];
+extern const char NS_JINGLE[];
+extern const char NS_JINGLE_DRAFT[];
+extern const char NS_GINGLE[];
 
 enum SignalingProtocol {
   PROTOCOL_JINGLE,
@@ -54,189 +54,187 @@
 };
 
 // actions (aka Gingle <session> or Jingle <jingle>)
-extern const buzz::QName QN_ACTION;
-extern const std::string LN_INITIATOR;
-extern const buzz::QName QN_INITIATOR;
-extern const buzz::QName QN_CREATOR;
+extern const buzz::StaticQName QN_ACTION;
+extern const char LN_INITIATOR[];
+extern const buzz::StaticQName QN_INITIATOR;
+extern const buzz::StaticQName QN_CREATOR;
 
-extern const buzz::QName QN_JINGLE;
-extern const buzz::QName QN_JINGLE_CONTENT;
-extern const buzz::QName QN_JINGLE_CONTENT_NAME;
-extern const buzz::QName QN_JINGLE_CONTENT_MEDIA;
-extern const buzz::QName QN_JINGLE_REASON;
-extern const std::string JINGLE_CONTENT_MEDIA_AUDIO;
-extern const std::string JINGLE_CONTENT_MEDIA_VIDEO;
-extern const std::string JINGLE_ACTION_SESSION_INITIATE;
-extern const std::string JINGLE_ACTION_SESSION_INFO;
-extern const std::string JINGLE_ACTION_SESSION_ACCEPT;
-extern const std::string JINGLE_ACTION_SESSION_TERMINATE;
-extern const std::string JINGLE_ACTION_TRANSPORT_INFO;
-extern const std::string JINGLE_ACTION_TRANSPORT_ACCEPT;
-extern const std::string JINGLE_ACTION_DESCRIPTION_INFO;
+extern const buzz::StaticQName QN_JINGLE;
+extern const buzz::StaticQName QN_JINGLE_CONTENT;
+extern const buzz::StaticQName QN_JINGLE_CONTENT_NAME;
+extern const buzz::StaticQName QN_JINGLE_CONTENT_MEDIA;
+extern const buzz::StaticQName QN_JINGLE_REASON;
+extern const char JINGLE_CONTENT_MEDIA_AUDIO[];
+extern const char JINGLE_CONTENT_MEDIA_VIDEO[];
+extern const char JINGLE_ACTION_SESSION_INITIATE[];
+extern const char JINGLE_ACTION_SESSION_INFO[];
+extern const char JINGLE_ACTION_SESSION_ACCEPT[];
+extern const char JINGLE_ACTION_SESSION_TERMINATE[];
+extern const char JINGLE_ACTION_TRANSPORT_INFO[];
+extern const char JINGLE_ACTION_TRANSPORT_ACCEPT[];
+extern const char JINGLE_ACTION_DESCRIPTION_INFO[];
 
-extern const buzz::QName QN_GINGLE_SESSION;
-extern const std::string GINGLE_ACTION_INITIATE;
-extern const std::string GINGLE_ACTION_INFO;
-extern const std::string GINGLE_ACTION_ACCEPT;
-extern const std::string GINGLE_ACTION_REJECT;
-extern const std::string GINGLE_ACTION_TERMINATE;
-extern const std::string GINGLE_ACTION_CANDIDATES;
-extern const std::string GINGLE_ACTION_UPDATE;
+extern const buzz::StaticQName QN_GINGLE_SESSION;
+extern const char GINGLE_ACTION_INITIATE[];
+extern const char GINGLE_ACTION_INFO[];
+extern const char GINGLE_ACTION_ACCEPT[];
+extern const char GINGLE_ACTION_REJECT[];
+extern const char GINGLE_ACTION_TERMINATE[];
+extern const char GINGLE_ACTION_CANDIDATES[];
+extern const char GINGLE_ACTION_UPDATE[];
 
-extern const std::string LN_ERROR;
-extern const buzz::QName QN_GINGLE_REDIRECT;
-extern const std::string STR_REDIRECT_PREFIX;
+extern const char LN_ERROR[];
+extern const buzz::StaticQName QN_GINGLE_REDIRECT;
+extern const char STR_REDIRECT_PREFIX[];
 
 // Session Contents (aka Gingle <session><description>
 //                   or Jingle <content><description>)
-extern const std::string LN_DESCRIPTION;
-extern const std::string LN_PAYLOADTYPE;
-extern const buzz::QName QN_ID;
-extern const buzz::QName QN_SID;
-extern const buzz::QName QN_NAME;
-extern const buzz::QName QN_CLOCKRATE;
-extern const buzz::QName QN_BITRATE;
-extern const buzz::QName QN_CHANNELS;
-extern const buzz::QName QN_WIDTH;
-extern const buzz::QName QN_HEIGHT;
-extern const buzz::QName QN_FRAMERATE;
-extern const buzz::QName QN_PARAMETER;
-extern const std::string LN_NAME;
-extern const std::string LN_VALUE;
-extern const buzz::QName QN_PAYLOADTYPE_PARAMETER_NAME;
-extern const buzz::QName QN_PAYLOADTYPE_PARAMETER_VALUE;
-extern const std::string PAYLOADTYPE_PARAMETER_BITRATE;
-extern const std::string PAYLOADTYPE_PARAMETER_HEIGHT;
-extern const std::string PAYLOADTYPE_PARAMETER_WIDTH;
-extern const std::string PAYLOADTYPE_PARAMETER_FRAMERATE;
-extern const std::string LN_BANDWIDTH;
+extern const char LN_DESCRIPTION[];
+extern const char LN_PAYLOADTYPE[];
+extern const buzz::StaticQName QN_ID;
+extern const buzz::StaticQName QN_SID;
+extern const buzz::StaticQName QN_NAME;
+extern const buzz::StaticQName QN_CLOCKRATE;
+extern const buzz::StaticQName QN_BITRATE;
+extern const buzz::StaticQName QN_CHANNELS;
+extern const buzz::StaticQName QN_PARAMETER;
+extern const char LN_NAME[];
+extern const char LN_VALUE[];
+extern const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_NAME;
+extern const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_VALUE;
+extern const char PAYLOADTYPE_PARAMETER_BITRATE[];
+extern const char PAYLOADTYPE_PARAMETER_HEIGHT[];
+extern const char PAYLOADTYPE_PARAMETER_WIDTH[];
+extern const char PAYLOADTYPE_PARAMETER_FRAMERATE[];
+extern const char LN_BANDWIDTH[];
 
 // CN_ == "content name".  When we initiate a session, we choose the
 // name, and when we receive a Gingle session, we provide default
 // names (since Gingle has no content names).  But when we receive a
 // Jingle call, the content name can be anything, so don't rely on
 // these values being the same as the ones received.
-extern const std::string CN_AUDIO;
-extern const std::string CN_VIDEO;
-extern const std::string CN_OTHER;
+extern const char CN_AUDIO[];
+extern const char CN_VIDEO[];
+extern const char CN_OTHER[];
+// other SDP related strings
+// GN stands for group name
+extern const char GN_BUNDLE[];
 
-extern const std::string NS_JINGLE_RTP;
-extern const buzz::QName QN_JINGLE_RTP_CONTENT;
-extern const buzz::QName QN_JINGLE_SSRC;
-extern const buzz::QName QN_JINGLE_RTP_PAYLOADTYPE;
-extern const buzz::QName QN_JINGLE_RTP_BANDWIDTH;
-extern const buzz::QName QN_JINGLE_RTCP_MUX;
+extern const char NS_JINGLE_RTP[];
+extern const buzz::StaticQName QN_JINGLE_RTP_CONTENT;
+extern const buzz::StaticQName QN_SSRC;
+extern const buzz::StaticQName QN_JINGLE_RTP_PAYLOADTYPE;
+extern const buzz::StaticQName QN_JINGLE_RTP_BANDWIDTH;
+extern const buzz::StaticQName QN_JINGLE_RTCP_MUX;
 
-extern const std::string NS_GINGLE_AUDIO;
-extern const buzz::QName QN_GINGLE_AUDIO_CONTENT;
-extern const buzz::QName QN_GINGLE_AUDIO_PAYLOADTYPE;
-extern const buzz::QName QN_GINGLE_AUDIO_SRCID;
-extern const std::string NS_GINGLE_VIDEO;
-extern const buzz::QName QN_GINGLE_VIDEO_CONTENT;
-extern const buzz::QName QN_GINGLE_VIDEO_PAYLOADTYPE;
-extern const buzz::QName QN_GINGLE_VIDEO_SRCID;
-extern const buzz::QName QN_GINGLE_VIDEO_BANDWIDTH;
+extern const char NS_GINGLE_AUDIO[];
+extern const buzz::StaticQName QN_GINGLE_AUDIO_CONTENT;
+extern const buzz::StaticQName QN_GINGLE_AUDIO_PAYLOADTYPE;
+extern const buzz::StaticQName QN_GINGLE_AUDIO_SRCID;
+extern const char NS_GINGLE_VIDEO[];
+extern const buzz::StaticQName QN_GINGLE_VIDEO_CONTENT;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_PAYLOADTYPE;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_SRCID;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_BANDWIDTH;
 
 // Crypto support.
-extern const buzz::QName QN_ENCRYPTION;
-extern const buzz::QName QN_ENCRYPTION_REQUIRED;
-extern const buzz::QName QN_CRYPTO;
-extern const buzz::QName QN_GINGLE_AUDIO_CRYPTO_USAGE;
-extern const buzz::QName QN_GINGLE_VIDEO_CRYPTO_USAGE;
-extern const buzz::QName QN_CRYPTO_SUITE;
-extern const buzz::QName QN_CRYPTO_KEY_PARAMS;
-extern const buzz::QName QN_CRYPTO_TAG;
-extern const buzz::QName QN_CRYPTO_SESSION_PARAMS;
+extern const buzz::StaticQName QN_ENCRYPTION;
+extern const buzz::StaticQName QN_ENCRYPTION_REQUIRED;
+extern const buzz::StaticQName QN_CRYPTO;
+extern const buzz::StaticQName QN_GINGLE_AUDIO_CRYPTO_USAGE;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_CRYPTO_USAGE;
+extern const buzz::StaticQName QN_CRYPTO_SUITE;
+extern const buzz::StaticQName QN_CRYPTO_KEY_PARAMS;
+extern const buzz::StaticQName QN_CRYPTO_TAG;
+extern const buzz::StaticQName QN_CRYPTO_SESSION_PARAMS;
 
 // transports and candidates
-extern const std::string LN_TRANSPORT;
-extern const std::string LN_CANDIDATE;
-extern const buzz::QName QN_JINGLE_P2P_TRANSPORT;
-extern const buzz::QName QN_JINGLE_P2P_CANDIDATE;
-extern const buzz::QName QN_UFRAG;
-extern const buzz::QName QN_COMPONENT;
-extern const buzz::QName QN_PWD;
-extern const buzz::QName QN_IP;
-extern const buzz::QName QN_PORT;
-extern const buzz::QName QN_NETWORK;
-extern const buzz::QName QN_GENERATION;
-extern const buzz::QName QN_PRIORITY;
-extern const buzz::QName QN_PROTOCOL;
-extern const std::string JINGLE_CANDIDATE_TYPE_PEER_STUN;
-extern const std::string JINGLE_CANDIDATE_TYPE_SERVER_STUN;
-extern const std::string JINGLE_CANDIDATE_NAME_RTP;
-extern const std::string JINGLE_CANDIDATE_NAME_RTCP;
+extern const char LN_TRANSPORT[];
+extern const char LN_CANDIDATE[];
+extern const buzz::StaticQName QN_JINGLE_P2P_TRANSPORT;
+extern const buzz::StaticQName QN_JINGLE_P2P_CANDIDATE;
+extern const buzz::StaticQName QN_UFRAG;
+extern const buzz::StaticQName QN_COMPONENT;
+extern const buzz::StaticQName QN_PWD;
+extern const buzz::StaticQName QN_IP;
+extern const buzz::StaticQName QN_PORT;
+extern const buzz::StaticQName QN_NETWORK;
+extern const buzz::StaticQName QN_GENERATION;
+extern const buzz::StaticQName QN_PRIORITY;
+extern const buzz::StaticQName QN_PROTOCOL;
+extern const char JINGLE_CANDIDATE_TYPE_PEER_STUN[];
+extern const char JINGLE_CANDIDATE_TYPE_SERVER_STUN[];
+extern const char JINGLE_CANDIDATE_NAME_RTP[];
+extern const char JINGLE_CANDIDATE_NAME_RTCP[];
 
-extern const std::string NS_GINGLE_P2P;
-extern const buzz::QName QN_GINGLE_P2P_TRANSPORT;
-extern const buzz::QName QN_GINGLE_P2P_CANDIDATE;
-extern const buzz::QName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME;
-extern const buzz::QName QN_GINGLE_CANDIDATE;
-extern const buzz::QName QN_ADDRESS;
-extern const buzz::QName QN_USERNAME;
-extern const buzz::QName QN_PASSWORD;
-extern const buzz::QName QN_PREFERENCE;
-extern const std::string GINGLE_CANDIDATE_TYPE_STUN;
-extern const std::string GINGLE_CANDIDATE_NAME_RTP;
-extern const std::string GINGLE_CANDIDATE_NAME_RTCP;
-extern const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTP;
-extern const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTCP;
+extern const char NS_GINGLE_P2P[];
+extern const buzz::StaticQName QN_GINGLE_P2P_TRANSPORT;
+extern const buzz::StaticQName QN_GINGLE_P2P_CANDIDATE;
+extern const buzz::StaticQName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME;
+extern const buzz::StaticQName QN_GINGLE_CANDIDATE;
+extern const buzz::StaticQName QN_ADDRESS;
+extern const buzz::StaticQName QN_USERNAME;
+extern const buzz::StaticQName QN_PASSWORD;
+extern const buzz::StaticQName QN_PREFERENCE;
+extern const char GINGLE_CANDIDATE_TYPE_STUN[];
+extern const char GINGLE_CANDIDATE_NAME_RTP[];
+extern const char GINGLE_CANDIDATE_NAME_RTCP[];
+extern const char GINGLE_CANDIDATE_NAME_VIDEO_RTP[];
+extern const char GINGLE_CANDIDATE_NAME_VIDEO_RTCP[];
 
-extern const std::string NS_GINGLE_RAW;
-extern const buzz::QName QN_GINGLE_RAW_TRANSPORT;
-extern const buzz::QName QN_GINGLE_RAW_CHANNEL;
+extern const char NS_GINGLE_RAW[];
+extern const buzz::StaticQName QN_GINGLE_RAW_TRANSPORT;
+extern const buzz::StaticQName QN_GINGLE_RAW_CHANNEL;
 
 // terminate reasons and errors: see http://xmpp.org/extensions/xep-0166.html
-extern const std::string JINGLE_ERROR_BAD_REQUEST;  // like parse error
+extern const char JINGLE_ERROR_BAD_REQUEST[];  // like parse error
 // got transport-info before session-initiate, for example
-extern const std::string JINGLE_ERROR_OUT_OF_ORDER;
-extern const std::string JINGLE_ERROR_UNKNOWN_SESSION;
+extern const char JINGLE_ERROR_OUT_OF_ORDER[];
+extern const char JINGLE_ERROR_UNKNOWN_SESSION[];
 
 // Call terminate reasons from XEP-166
-extern const std::string STR_TERMINATE_DECLINE;  // polite reject
-extern const std::string STR_TERMINATE_SUCCESS;  // polite hangup
-extern const std::string STR_TERMINATE_ERROR;  // something bad happened
-extern const std::string STR_TERMINATE_INCOMPATIBLE_PARAMETERS;  // no codecs?
+extern const char STR_TERMINATE_DECLINE[];  // polite reject
+extern const char STR_TERMINATE_SUCCESS[];  // polite hangup
+extern const char STR_TERMINATE_ERROR[];  // something bad happened
+extern const char STR_TERMINATE_INCOMPATIBLE_PARAMETERS[];  // no codecs?
 
 // Old terminate reasons used by cricket
-extern const std::string STR_TERMINATE_CALL_ENDED;
-extern const std::string STR_TERMINATE_RECIPIENT_UNAVAILABLE;
-extern const std::string STR_TERMINATE_RECIPIENT_BUSY;
-extern const std::string STR_TERMINATE_INSUFFICIENT_FUNDS;
-extern const std::string STR_TERMINATE_NUMBER_MALFORMED;
-extern const std::string STR_TERMINATE_NUMBER_DISALLOWED;
-extern const std::string STR_TERMINATE_PROTOCOL_ERROR;
-extern const std::string STR_TERMINATE_INTERNAL_SERVER_ERROR;
-extern const std::string STR_TERMINATE_UNKNOWN_ERROR;
+extern const char STR_TERMINATE_CALL_ENDED[];
+extern const char STR_TERMINATE_RECIPIENT_UNAVAILABLE[];
+extern const char STR_TERMINATE_RECIPIENT_BUSY[];
+extern const char STR_TERMINATE_INSUFFICIENT_FUNDS[];
+extern const char STR_TERMINATE_NUMBER_MALFORMED[];
+extern const char STR_TERMINATE_NUMBER_DISALLOWED[];
+extern const char STR_TERMINATE_PROTOCOL_ERROR[];
+extern const char STR_TERMINATE_INTERNAL_SERVER_ERROR[];
+extern const char STR_TERMINATE_UNKNOWN_ERROR[];
 
 // Draft view and notify messages.
-extern const buzz::QName QN_JINGLE_DRAFT_CONTENT_NAME;
-extern const std::string STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO;
-extern const std::string STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO;
-extern const buzz::QName QN_JINGLE_DRAFT_NOTIFY;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE_NICK;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE_NAME;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE_USAGE;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE_STATE;
-extern const std::string STR_JINGLE_DRAFT_SOURCE_STATE_REMOVED;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE_SSRC;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_NAME;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_TYPE;
-extern const std::string STR_JINGLE_DRAFT_VIEW_TYPE_NONE;
-extern const std::string STR_JINGLE_DRAFT_VIEW_TYPE_STATIC;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_SSRC;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_WIDTH;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_HEIGHT;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_FRAMERATE;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_PREFERENCE;
+extern const char STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO[];
+extern const char STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO[];
+extern const buzz::StaticQName QN_NICK;
+extern const buzz::StaticQName QN_TYPE;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_VIEW;
+extern const char STR_JINGLE_DRAFT_VIEW_TYPE_NONE[];
+extern const char STR_JINGLE_DRAFT_VIEW_TYPE_STATIC[];
+extern const buzz::StaticQName QN_JINGLE_DRAFT_PARAMS;
+extern const buzz::StaticQName QN_WIDTH;
+extern const buzz::StaticQName QN_HEIGHT;
+extern const buzz::StaticQName QN_FRAMERATE;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_STREAM;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_STREAMS;
+extern const buzz::StaticQName QN_DISPLAY;
+extern const buzz::StaticQName QN_CNAME;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_SSRC;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_SSRC_GROUP;
+extern const buzz::StaticQName QN_SEMANTICS;
+extern const buzz::StaticQName QN_JINGLE_LEGACY_NOTIFY;
+extern const buzz::StaticQName QN_JINGLE_LEGACY_SOURCE;
 
 // old stuff
 #ifdef FEATURE_ENABLE_VOICEMAIL
-extern const std::string NS_VOICEMAIL;
-extern const buzz::QName QN_VOICEMAIL_REGARDING;
+extern const char NS_VOICEMAIL[];
+extern const buzz::StaticQName QN_VOICEMAIL_REGARDING;
 #endif
 
 }  // namespace cricket
diff --git a/talk/p2p/base/fakesession.h b/talk/p2p/base/fakesession.h
new file mode 100644
index 0000000..1928613
--- /dev/null
+++ b/talk/p2p/base/fakesession.h
@@ -0,0 +1,233 @@
+// libjingle
+// Copyright 2009 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_SESSION_PHONE_FAKESESSION_H_
+#define TALK_SESSION_PHONE_FAKESESSION_H_
+
+#include <map>
+#include <string>
+
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+
+namespace cricket {
+
+class FakeTransport;
+
+// Fake transport channel class, which can be passed to anything that needs a
+// transport channel. Can be informed of another FakeTransportChannel via
+// SetDestination.
+class FakeTransportChannel : public TransportChannelImpl {
+ public:
+  explicit FakeTransportChannel(Transport* transport,
+                                const std::string& name,
+                                const std::string& session_type)
+      : TransportChannelImpl(name, session_type),
+        transport_(transport),
+        dest_(NULL),
+        state_(STATE_INIT) {
+  }
+  ~FakeTransportChannel() {
+    Reset();
+  }
+
+  virtual Transport* GetTransport() {
+    return transport_;
+  }
+  virtual void Connect() {
+    if (state_ == STATE_INIT) {
+      state_ = STATE_CONNECTING;
+    }
+  }
+  virtual void Reset() {
+    if (state_ != STATE_INIT) {
+      state_ = STATE_INIT;
+      if (dest_) {
+        dest_->state_ = STATE_INIT;
+        dest_->dest_ = NULL;
+        dest_ = NULL;
+      }
+    }
+  }
+
+  void SetDestination(FakeTransportChannel* dest) {
+    if (state_ == STATE_CONNECTING && dest) {
+      // This simulates the delivery of candidates.
+      dest_ = dest;
+      dest_->dest_ = this;
+      state_ = STATE_CONNECTED;
+      dest_->state_ = STATE_CONNECTED;
+      set_writable(true);
+      dest_->set_writable(true);
+    } else if (state_ == STATE_CONNECTED && !dest) {
+      // Simulates loss of connectivity, by asymmetrically forgetting dest_.
+      dest_ = NULL;
+      state_ = STATE_CONNECTING;
+      set_writable(false);
+    }
+  }
+
+  virtual int SendPacket(const char *data, size_t len) {
+    if (state_ != STATE_CONNECTED) {
+      return -1;
+    }
+    dest_->SignalReadPacket(dest_, data, len);
+    return len;
+  }
+  virtual int SetOption(talk_base::Socket::Option opt, int value) {
+    return true;
+  }
+  virtual int GetError() {
+    return 0;
+  }
+
+  virtual void OnSignalingReady() {
+  }
+  virtual void OnCandidate(const Candidate& candidate) {
+  }
+
+ private:
+  enum State { STATE_INIT, STATE_CONNECTING, STATE_CONNECTED };
+  Transport* transport_;
+  FakeTransportChannel* dest_;
+  State state_;
+};
+
+// Fake transport class, which can be passed to anything that needs a Transport.
+// Can be informed of another FakeTransport via SetDestination (low-tech way
+// of doing candidates)
+class FakeTransport : public Transport {
+ public:
+  typedef std::map<std::string, FakeTransportChannel*> ChannelMap;
+  FakeTransport(talk_base::Thread* signaling_thread,
+                talk_base::Thread* worker_thread)
+      : Transport(signaling_thread, worker_thread, "test", NULL),
+        dest_(NULL) {
+  }
+  ~FakeTransport() {
+    DestroyAllChannels();
+  }
+
+  const ChannelMap& channels() const { return channels_; }
+
+  void SetDestination(FakeTransport* dest) {
+    dest_ = dest;
+    for (ChannelMap::iterator it = channels_.begin(); it != channels_.end();
+         ++it) {
+      SetChannelDestination(it->first, it->second);
+    }
+  }
+
+ protected:
+  virtual TransportChannelImpl* CreateTransportChannel(
+      const std::string& name, const std::string& session_type) {
+    if (channels_.find(name) != channels_.end()) {
+      return NULL;
+    }
+    FakeTransportChannel* channel =
+        new FakeTransportChannel(this, name, session_type);
+    SetChannelDestination(name, channel);
+    channels_[name] = channel;
+    return channel;
+  }
+  virtual void DestroyTransportChannel(TransportChannelImpl* channel) {
+    channels_.erase(channel->name());
+    delete channel;
+  }
+
+ private:
+  void SetChannelDestination(const std::string& name,
+                             FakeTransportChannel* channel) {
+    FakeTransportChannel* dest_channel = NULL;
+    if (dest_) {
+      dest_channel =
+          static_cast<FakeTransportChannel*>(dest_->GetChannel(name));
+    }
+    channel->SetDestination(dest_channel);
+  }
+
+  ChannelMap channels_;
+  FakeTransport* dest_;
+};
+
+// Fake session class, which can be passed into a BaseChannel object for
+// test purposes. Can be connected to other FakeSessions via Connect().
+class FakeSession : public BaseSession {
+ public:
+  FakeSession()
+      : BaseSession(talk_base::Thread::Current(),
+                    talk_base::Thread::Current(),
+                    NULL, "", "", true),
+        fail_create_channel_(false) {
+  }
+
+  FakeTransport* GetTransport(const std::string& content_name) {
+    return static_cast<FakeTransport*>(
+        BaseSession::GetTransport(content_name));
+  }
+
+  void Connect(FakeSession* dest) {
+    // Simulate the exchange of candidates.
+    CompleteNegotiation();
+    dest->CompleteNegotiation();
+    for (TransportMap::const_iterator it = transport_proxies().begin();
+        it != transport_proxies().end(); ++it) {
+      static_cast<FakeTransport*>(it->second->impl())->SetDestination(
+          dest->GetTransport(it->first));
+    }
+  }
+
+  virtual cricket::TransportChannel* CreateChannel(
+      const std::string& content_name, const std::string& name) {
+    if (fail_create_channel_) {
+      return NULL;
+    }
+    return BaseSession::CreateChannel(content_name, name);
+  }
+
+  void set_fail_channel_creation(bool fail_channel_creation) {
+    fail_create_channel_ = fail_channel_creation;
+  }
+
+ protected:
+  virtual Transport* CreateTransport() {
+    return new FakeTransport(signaling_thread(), worker_thread());
+  }
+  void CompleteNegotiation() {
+    for (TransportMap::const_iterator it = transport_proxies().begin();
+        it != transport_proxies().end(); ++it) {
+      it->second->CompleteNegotiation();
+    }
+  }
+
+ private:
+  bool fail_create_channel_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FAKESESSION_H_
diff --git a/talk/p2p/base/p2ptransport.cc b/talk/p2p/base/p2ptransport.cc
index dd170ff..5f55ef9 100644
--- a/talk/p2p/base/p2ptransport.cc
+++ b/talk/p2p/base/p2ptransport.cc
@@ -143,7 +143,7 @@
   return true;
 }
 
-const buzz::QName& GetCandidateQName(SignalingProtocol protocol) {
+static const buzz::StaticQName& GetCandidateQName(SignalingProtocol protocol) {
   if (protocol == PROTOCOL_GINGLE) {
     return QN_GINGLE_CANDIDATE;
   } else {
diff --git a/talk/p2p/base/p2ptransportchannel.cc b/talk/p2p/base/p2ptransportchannel.cc
index 17f52a7..87a0222 100644
--- a/talk/p2p/base/p2ptransportchannel.cc
+++ b/talk/p2p/base/p2ptransportchannel.cc
@@ -301,7 +301,7 @@
 // Handle stun packets
 void P2PTransportChannel::OnUnknownAddress(
     Port *port, const talk_base::SocketAddress &address, StunMessage *stun_msg,
-    const std::string &remote_username) {
+    const std::string &remote_username, bool port_muxed) {
   ASSERT(worker_thread_ == talk_base::Thread::Current());
 
   // Port has received a valid stun packet from an address that no Connection
@@ -316,11 +316,17 @@
       break;
     }
   }
+
   if (candidate == NULL) {
+    if (port_muxed) {
+      // When Ports are muxed, SignalUnknownAddress is delivered to all
+      // P2PTransportChannel belong to a session. Return from here will
+      // save us from sending stun binding error message from incorrect channel.
+      return;
+    }
     // Don't know about this username, the request is bogus
     // This sometimes happens if a binding response comes in before the ACCEPT
     // message.  It is totally valid; the retry state machine will try again.
-
     port->SendBindingErrorResponse(stun_msg, address,
         STUN_ERROR_STALE_CREDENTIALS, STUN_ERROR_REASON_STALE_CREDENTIALS);
     delete stun_msg;
@@ -451,6 +457,13 @@
   return true;
 }
 
+bool P2PTransportChannel::FindConnection(
+    cricket::Connection* connection) const {
+  std::vector<Connection*>::const_iterator citer =
+      std::find(connections_.begin(), connections_.end(), connection);
+  return citer != connections_.end();
+}
+
 // Maintain our remote candidate list, adding this new remote one.
 void P2PTransportChannel::RememberRemoteCandidate(
     const Candidate& remote_candidate, Port* origin_port) {
@@ -900,8 +913,11 @@
                                        const char *data, size_t len) {
   ASSERT(worker_thread_ == talk_base::Thread::Current());
 
-  // Let the client know of an incoming packet
+  // Do not deliver, if packet doesn't belong to the correct transport channel.
+  if (!FindConnection(connection))
+    return;
 
+  // Let the client know of an incoming packet
   SignalReadPacket(this, data, len);
 }
 
@@ -933,7 +949,8 @@
 void P2PTransportChannel::OnSignalingReady() {
   if (waiting_for_signaling_) {
     waiting_for_signaling_ = false;
-    AddAllocatorSession(allocator_->CreateSession(name(), content_type()));
+    AddAllocatorSession(allocator_->CreateSession(
+        session_id(), name(), content_type()));
     thread()->PostDelayed(kAllocatePeriod, this, MSG_ALLOCATE);
   }
 }
diff --git a/talk/p2p/base/p2ptransportchannel.h b/talk/p2p/base/p2ptransportchannel.h
index 0083d23..6b4d979 100644
--- a/talk/p2p/base/p2ptransportchannel.h
+++ b/talk/p2p/base/p2ptransportchannel.h
@@ -116,11 +116,12 @@
                          bool readable);
   bool CreateConnection(Port* port, const Candidate& remote_candidate,
                         Port* origin_port, bool readable);
+  bool FindConnection(cricket::Connection* connection) const;
   void RememberRemoteCandidate(const Candidate& remote_candidate,
                                Port* origin_port);
   void OnUnknownAddress(Port *port, const talk_base::SocketAddress &addr,
                         StunMessage *stun_msg,
-                        const std::string &remote_username);
+                        const std::string &remote_username, bool port_muxed);
   void OnPortReady(PortAllocatorSession *session, Port* port);
   void OnCandidatesReady(PortAllocatorSession *session,
                          const std::vector<Candidate>& candidates);
diff --git a/talk/p2p/base/p2ptransportchannel_unittest.cc b/talk/p2p/base/p2ptransportchannel_unittest.cc
new file mode 100644
index 0000000..ae5ab62
--- /dev/null
+++ b/talk/p2p/base/p2ptransportchannel_unittest.cc
@@ -0,0 +1,659 @@
+/*
+ * libjingle
+ * Copyright 2009 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/fakenetwork.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/natserver.h"
+#include "talk/base/natsocketfactory.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/proxyserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+#include "talk/p2p/base/testrelayserver.h"
+#include "talk/p2p/base/teststunserver.h"
+#include "talk/p2p/client/basicportallocator.h"
+
+using talk_base::SocketAddress;
+
+static const int kDefaultTimeout = 1000;
+static const int kOnlyLocalPorts = cricket::PORTALLOCATOR_DISABLE_STUN |
+                                   cricket::PORTALLOCATOR_DISABLE_RELAY |
+                                   cricket::PORTALLOCATOR_DISABLE_TCP;
+// Addresses on the public internet.
+static const SocketAddress kPublicAddrs[2] =
+    { SocketAddress("11.11.11.11", 0), SocketAddress("22.22.22.22", 0) };
+// For configuring multihomed clients.
+static const SocketAddress kAlternateAddrs[2] =
+    { SocketAddress("11.11.11.101", 0), SocketAddress("22.22.22.202", 0) };
+// Addresses for HTTP proxy servers.
+static const SocketAddress kHttpsProxyAddrs[2] =
+    { SocketAddress("11.11.11.1", 443), SocketAddress("22.22.22.1", 443) };
+// Addresses for SOCKS proxy servers.
+static const SocketAddress kSocksProxyAddrs[2] =
+    { SocketAddress("11.11.11.1", 1080), SocketAddress("22.22.22.1", 1080) };
+// Internal addresses for NAT boxes.
+static const SocketAddress kNatAddrs[2] =
+    { SocketAddress("192.168.1.1", 0), SocketAddress("192.168.2.1", 0) };
+// Private addresses inside the NAT private networks.
+static const SocketAddress kPrivateAddrs[2] =
+    { SocketAddress("192.168.1.11", 0), SocketAddress("192.168.2.22", 0) };
+// For cascaded NATs, the internal addresses of the inner NAT boxes.
+static const SocketAddress kCascadedNatAddrs[2] =
+    { SocketAddress("192.168.10.1", 0), SocketAddress("192.168.20.1", 0) };
+// For cascaded NATs, private addresses inside the inner private networks.
+static const SocketAddress kCascadedPrivateAddrs[2] =
+    { SocketAddress("192.168.10.11", 0), SocketAddress("192.168.20.22", 0) };
+// The address of the public STUN server.
+static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
+// The addresses for the public relay server.
+static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000);
+static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
+static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002);
+static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003);
+static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004);
+static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005);
+
+// This test simulates 2 P2P endpoints that want to establish connectivity
+// with each other over various network topologies and conditions, which can be
+// specified in each individial test.
+// A virtual network (via VirtualSocketServer) along with virtual firewalls and
+// NATs (via Firewall/NATSocketServer) are used to simulate the various network
+// conditions. We can configure the IP addresses of the endpoints,
+// block various types of connectivity, or add arbitrary levels of NAT.
+// We also run a STUN server and a relay server on the virtual network to allow
+// our typical P2P mechanisms to do their thing.
+// For each case, we expect the P2P stack to eventually settle on a specific
+// form of connectivity to the other side. The test checks that the P2P
+// negotiation successfully establishes connectivity within a certain time,
+// and that the result is what we expect.
+// Note that this class is a base class for use by other tests, who will provide
+// specialized test behavior.
+class P2PTransportChannelTestBase : public testing::Test,
+                                    public sigslot::has_slots<> {
+ public:
+  P2PTransportChannelTestBase()
+      : main_(talk_base::Thread::Current()),
+        pss_(new talk_base::PhysicalSocketServer),
+        vss_(new talk_base::VirtualSocketServer(pss_.get())),
+        nss_(new talk_base::NATSocketServer(vss_.get())),
+        ss_(new talk_base::FirewallSocketServer(nss_.get())),
+        ss_scope_(ss_.get()),
+        stun_server_(main_, kStunAddr),
+        relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr,
+                      kRelayTcpIntAddr, kRelayTcpExtAddr,
+                      kRelaySslTcpIntAddr, kRelaySslTcpExtAddr),
+        socks_server1_(ss_.get(), kSocksProxyAddrs[0],
+                       ss_.get(), kSocksProxyAddrs[0]),
+        socks_server2_(ss_.get(), kSocksProxyAddrs[1],
+                       ss_.get(), kSocksProxyAddrs[1]),
+        allocator1_(&network_manager1_, kStunAddr,
+                    kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr),
+        allocator2_(&network_manager2_, kStunAddr,
+                    kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr) {
+  }
+
+ protected:
+  enum Config {
+    OPEN,                           // Open to the Internet
+    NAT_FULL_CONE,                  // NAT, no filtering
+    NAT_ADDR_RESTRICTED,            // NAT, must send to an addr to recv
+    NAT_PORT_RESTRICTED,            // NAT, must send to an addr+port to recv
+    NAT_SYMMETRIC,                  // NAT, endpoint-dependent bindings
+    NAT_DOUBLE_CONE,                // Double NAT, both cone
+    NAT_SYMMETRIC_THEN_CONE,        // Double NAT, symmetric outer, cone inner
+    BLOCK_UDP,                      // Firewall, UDP in/out blocked
+    BLOCK_UDP_AND_INCOMING_TCP,     // Firewall, UDP in/out and TCP in blocked
+    BLOCK_ALL_BUT_OUTGOING_HTTP,    // Firewall, only TCP out on 80/443
+    PROXY_HTTPS,                    // All traffic through HTTPS proxy
+    PROXY_SOCKS,                    // All traffic through SOCKS proxy
+    NUM_CONFIGS
+  };
+
+  struct Result {
+    Result(const std::string& lt, const std::string lp,
+           const std::string& rt, const std::string rp, int wait)
+        : local_type(lt), local_proto(lp), remote_type(rt), remote_proto(rp),
+          connect_wait(wait) {
+    }
+    std::string local_type;
+    std::string local_proto;
+    std::string remote_type;
+    std::string remote_proto;
+    int connect_wait;
+  };
+
+  // Common results.
+  static const Result kLocalUdpToLocalUdp;
+  static const Result kLocalUdpToStunUdp;
+  static const Result kStunUdpToLocalUdp;
+  static const Result kStunUdpToStunUdp;
+  static const Result kLocalUdpToRelayUdp;
+  static const Result kLocalTcpToLocalTcp;
+
+  static void SetUpTestCase() {
+    // Ensure the RNG is inited.
+    talk_base::InitRandom(NULL, 0);
+  }
+
+  talk_base::NATSocketServer* nat() { return nss_.get(); }
+  talk_base::FirewallSocketServer* fw() { return ss_.get(); }
+
+  cricket::PortAllocator* GetAllocator(int endpoint) {
+    return (endpoint == 0) ? &allocator1_ : &allocator2_;
+  }
+  void AddAddress(int endpoint, const SocketAddress& addr) {
+    talk_base::FakeNetworkManager& manager = (endpoint == 0) ?
+        network_manager1_ : network_manager2_;
+    manager.AddInterface(addr);
+  }
+  void RemoveAddress(int endpoint, const SocketAddress& addr) {
+    talk_base::FakeNetworkManager& manager = (endpoint == 0) ?
+        network_manager1_ : network_manager2_;
+    manager.RemoveInterface(addr);
+  }
+  void SetProxy(int endpoint, talk_base::ProxyType type) {
+    talk_base::ProxyInfo info;
+    info.type = type;
+    info.address = (type == talk_base::PROXY_HTTPS) ?
+        kHttpsProxyAddrs[endpoint] : kSocksProxyAddrs[endpoint];
+    GetAllocator(endpoint)->set_proxy("unittest/1.0", info);
+  }
+  void SetAllocatorFlags(int endpoint, int flags) {
+    GetAllocator(endpoint)->set_flags(flags);
+  }
+
+  void Test(const Result& expected) {
+    int32 connect_start = talk_base::Time(), connect_time;
+
+    // Create the channels and wait for them to connect.
+    CreateChannels();
+    EXPECT_TRUE_WAIT_MARGIN(ch1_.get() != NULL && ch2_.get() != NULL &&
+                            ch1_->readable() && ch1_->writable() &&
+                            ch2_->readable() && ch2_->writable(),
+                            expected.connect_wait,
+                            1000);
+    connect_time = talk_base::TimeSince(connect_start);
+    if (connect_time < expected.connect_wait) {
+      LOG(LS_INFO) << "Connect time: " << connect_time << " ms";
+    } else {
+      LOG(LS_INFO) << "Connect time: " << "TIMEOUT ("
+                   << expected.connect_wait << " ms)";
+    }
+
+    // Allow a few turns of the crank for the best connections to emerge.
+    // This may take up to 2 seconds.
+    if (ch1_->best_connection() && ch2_->best_connection()) {
+      int32 converge_start = talk_base::Time(), converge_time;
+      int converge_wait = 2000;
+      EXPECT_TRUE_WAIT_MARGIN(
+          LocalCandidate(ch1_.get())->type() == expected.local_type &&
+          LocalCandidate(ch1_.get())->protocol() == expected.local_proto &&
+          RemoteCandidate(ch1_.get())->type() == expected.remote_type &&
+          RemoteCandidate(ch1_.get())->protocol() == expected.remote_proto,
+          converge_wait,
+          converge_wait);
+
+      // Also do EXPECT_EQ on each part so that failures are more verbose.
+      EXPECT_EQ(expected.local_type, LocalCandidate(ch1_.get())->type());
+      EXPECT_EQ(expected.local_proto, LocalCandidate(ch1_.get())->protocol());
+      EXPECT_EQ(expected.remote_type, RemoteCandidate(ch1_.get())->type());
+      EXPECT_EQ(expected.remote_proto, RemoteCandidate(ch1_.get())->protocol());
+
+      /* TODO: Check ch2 candidates.
+      EXPECT_EQ(expected.local_type2, LocalCandidate(ch1_.get())->type());
+      EXPECT_EQ(expected.local_proto2, LocalCandidate(ch1_.get())->protocol());
+      EXPECT_EQ(expected.remote_type2, RemoteCandidate(ch1_.get())->type());
+      EXPECT_EQ(expected.remote_proto2, RemoteCandidate(ch1_.get())->protocol());
+      */
+
+      converge_time = talk_base::TimeSince(converge_start);
+      if (converge_time < converge_wait) {
+        LOG(LS_INFO) << "Converge time: " << converge_time << " ms";
+      } else {
+        LOG(LS_INFO) << "Converge time: " << "TIMEOUT ("
+                     << converge_wait << " ms)";
+      }
+    }
+
+    // TODO: Send some data and make sure it gets there.
+
+    // Destroy the channels, and wait for them to be fully cleaned up.
+    DestroyChannels();
+  }
+
+  void CreateChannels() {
+    ch1_.reset(new cricket::P2PTransportChannel("a", "unittest",
+                                                NULL, &allocator1_));
+    ch2_.reset(new cricket::P2PTransportChannel("b", "unittest",
+                                                NULL, &allocator2_));
+    ch1_->SignalRequestSignaling.connect(ch1_.get(),
+        &cricket::P2PTransportChannel::OnSignalingReady);
+    ch2_->SignalRequestSignaling.connect(ch2_.get(),
+        &cricket::P2PTransportChannel::OnSignalingReady);
+    ch1_->SignalCandidateReady.connect(this,
+        &P2PTransportChannelTestBase::OnCandidate);
+    ch2_->SignalCandidateReady.connect(this,
+        &P2PTransportChannelTestBase::OnCandidate);
+    ch1_->Connect();
+    ch2_->Connect();
+  }
+  void DestroyChannels() {
+    ch1_.reset();
+    ch2_.reset();
+  }
+  cricket::P2PTransportChannel* ch1() { return ch1_.get(); }
+  cricket::P2PTransportChannel* ch2() { return ch2_.get(); }
+
+  // We pass the candidates directly to the other side.
+  void OnCandidate(cricket::TransportChannelImpl* ch,
+                   const cricket::Candidate& c) {
+    if (ch == ch1_.get()) {
+      LOG(LS_INFO) << "Candidate(1->2): " << c.type() << ", " << c.protocol()
+                   << ", " << c.address().ToString() << ", " << c.username()
+                   << ", " << c.generation();
+      ch2_->OnCandidate(c);
+    } else {
+      LOG(LS_INFO) << "Candidate(2->1): " << c.type() << ", " << c.protocol()
+                   << ", " << c.address().ToString() << ", " << c.username()
+                   << ", " << c.generation();
+      ch1_->OnCandidate(c);
+    }
+  }
+  static const cricket::Candidate* LocalCandidate(
+      cricket::P2PTransportChannel* ch) {
+    return (ch && ch->best_connection()) ?
+        &ch->best_connection()->local_candidate() : NULL;
+  }
+  static const cricket::Candidate* RemoteCandidate(
+      cricket::P2PTransportChannel* ch) {
+    return (ch && ch->best_connection()) ?
+        &ch->best_connection()->remote_candidate() : NULL;
+  }
+
+ private:
+  enum { MSG_CONNECT, MSG_DISCONNECT };
+  talk_base::Thread* main_;
+  talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
+  talk_base::scoped_ptr<talk_base::VirtualSocketServer> vss_;
+  talk_base::scoped_ptr<talk_base::NATSocketServer> nss_;
+  talk_base::scoped_ptr<talk_base::FirewallSocketServer> ss_;
+  talk_base::SocketServerScope ss_scope_;
+  cricket::TestStunServer stun_server_;
+  cricket::TestRelayServer relay_server_;
+  talk_base::SocksProxyServer socks_server1_;
+  talk_base::SocksProxyServer socks_server2_;
+  talk_base::FakeNetworkManager network_manager1_;
+  talk_base::FakeNetworkManager network_manager2_;
+  cricket::BasicPortAllocator allocator1_;
+  cricket::BasicPortAllocator allocator2_;
+  talk_base::scoped_ptr<cricket::P2PTransportChannel> ch1_;
+  talk_base::scoped_ptr<cricket::P2PTransportChannel> ch2_;
+};
+
+// The tests have only a few outcomes, which we predefine.
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kLocalUdpToLocalUdp("local", "udp", "local", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kLocalUdpToStunUdp("local", "udp", "stun", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kStunUdpToLocalUdp("stun", "udp", "local", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kStunUdpToStunUdp("stun", "udp", "stun", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kLocalUdpToRelayUdp("local", "udp", "relay", "udp", 2000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kLocalTcpToLocalTcp("local", "tcp", "local", "tcp", 3000);
+
+// Test the matrix of all the connectivity types we expect to see in the wild.
+// Just test every combination of the configs in the Config enum.
+class P2PTransportChannelTest : public P2PTransportChannelTestBase {
+ protected:
+  static const Result* kMatrix[NUM_CONFIGS][NUM_CONFIGS];
+  void ConfigureEndpoints(Config config1, Config config2) {
+    ConfigureEndpoint(0, config1);
+    ConfigureEndpoint(1, config2);
+  }
+  void ConfigureEndpoint(int endpoint, Config config) {
+    switch (config) {
+      case OPEN:
+        AddAddress(endpoint, kPublicAddrs[endpoint]);
+        break;
+      case NAT_FULL_CONE:
+      case NAT_ADDR_RESTRICTED:
+      case NAT_PORT_RESTRICTED:
+      case NAT_SYMMETRIC:
+        AddAddress(endpoint, kPrivateAddrs[endpoint]);
+        // Add a single NAT of the desired type
+        nat()->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint],
+            static_cast<talk_base::NATType>(config - NAT_FULL_CONE))->
+            AddClient(kPrivateAddrs[endpoint]);
+        break;
+      case NAT_DOUBLE_CONE:
+      case NAT_SYMMETRIC_THEN_CONE:
+        AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]);
+        // Add a two cascaded NATs of the desired types
+        nat()->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint],
+            (config == NAT_DOUBLE_CONE) ?
+                talk_base::NAT_OPEN_CONE : talk_base::NAT_SYMMETRIC)->
+            AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint],
+                talk_base::NAT_OPEN_CONE)->
+                AddClient(kCascadedPrivateAddrs[endpoint]);
+        break;
+      case BLOCK_UDP:
+      case BLOCK_UDP_AND_INCOMING_TCP:
+      case BLOCK_ALL_BUT_OUTGOING_HTTP:
+      case PROXY_HTTPS:
+      case PROXY_SOCKS:
+        AddAddress(endpoint, kPublicAddrs[endpoint]);
+        // Block all UDP
+        fw()->AddRule(false, talk_base::FP_UDP, talk_base::FD_ANY,
+                      kPublicAddrs[endpoint]);
+        if (config == BLOCK_UDP_AND_INCOMING_TCP) {
+          // Block TCP inbound to the endpoint
+          fw()->AddRule(false, talk_base::FP_TCP, SocketAddress(),
+                        kPublicAddrs[endpoint]);
+        } else if (config == BLOCK_ALL_BUT_OUTGOING_HTTP) {
+          // Block all TCP to/from the endpoint except 80/443 out
+          fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
+                        SocketAddress(talk_base::IPAddress(INADDR_ANY), 80));
+          fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
+                        SocketAddress(talk_base::IPAddress(INADDR_ANY), 443));
+          fw()->AddRule(false, talk_base::FP_TCP, talk_base::FD_ANY,
+                        kPublicAddrs[endpoint]);
+        } else if (config == PROXY_HTTPS) {
+          // Block all TCP to/from the endpoint except to the proxy server
+          fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
+                        kHttpsProxyAddrs[endpoint]);
+          fw()->AddRule(false, talk_base::FP_TCP, talk_base::FD_ANY,
+                        kPublicAddrs[endpoint]);
+          SetProxy(endpoint, talk_base::PROXY_HTTPS);
+        } else if (config == PROXY_SOCKS) {
+          // Block all TCP to/from the endpoint except to the proxy server
+          fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
+                        kSocksProxyAddrs[endpoint]);
+          fw()->AddRule(false, talk_base::FP_TCP, talk_base::FD_ANY,
+                        kPublicAddrs[endpoint]);
+          SetProxy(endpoint, talk_base::PROXY_SOCKS5);
+        }
+        break;
+      default:
+        break;
+    }
+  }
+};
+
+// Shorthands for use in the test matrix.
+#define LULU &kLocalUdpToLocalUdp
+#define LUSU &kLocalUdpToStunUdp
+#define SULU &kStunUdpToLocalUdp
+#define SUSU &kStunUdpToStunUdp
+#define LURU &kLocalUdpToRelayUdp
+#define LTLT &kLocalTcpToLocalTcp
+// TODO: Enable these once TestRelayServer can accept external TCP.
+#define LTRT NULL
+#define LSRS NULL
+
+// Test matrix. Originator behavior defined by rows, receiever by columns.
+// TODO: Fix NULLs caused by lack of TCP support in NATSocket.
+// TODO: Fix NULLs caused by no HTTP proxy support.
+// TODO: Rearrange rows/columns from best to worst.
+const P2PTransportChannelTest::Result*
+    P2PTransportChannelTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = {
+//      OPEN  CONE  ADDR  PORT  SYMM  2CON  SCON  !UDP  !TCP  HTTP  PRXH  PRXS
+/*OP*/ {LULU, LULU, LULU, LULU, LULU, LULU, LULU, LTLT, LTLT, LSRS, NULL, LTLT},
+/*CO*/ {LULU, LULU, LULU, SULU, SULU, LULU, SULU, NULL, NULL, LSRS, NULL, LTRT},
+/*AD*/ {LULU, LULU, LULU, SUSU, SUSU, LULU, SUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*PO*/ {LULU, LUSU, SUSU, SUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*SY*/ {LULU, LUSU, SUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*2C*/ {LULU, LULU, LULU, SULU, SULU, LULU, SULU, NULL, NULL, LSRS, NULL, LTRT},
+/*SC*/ {LULU, LUSU, SUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*!U*/ {LTLT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTLT, LSRS, NULL, LTRT},
+/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTRT, LSRS, NULL, LTRT},
+/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS},
+/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},
+/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT},
+};
+
+// The actual tests that exercise all the various configurations.
+// Test names are of the form P2PTransportChannelTest_TestOPENToNAT_FULL_CONE
+#define P2P_TEST_DECLARATION(x, y, z) \
+  TEST_F(P2PTransportChannelTest, z##Test##x##To##y) { \
+    ConfigureEndpoints(x, y); \
+    if (kMatrix[x][y] != NULL) \
+      Test(*kMatrix[x][y]); \
+    else \
+      LOG(LS_WARNING) << "Not yet implemented"; \
+  }
+
+#define P2P_TEST(x, y) \
+  P2P_TEST_DECLARATION(x, y,)
+
+#define FLAKY_P2P_TEST(x, y) \
+  P2P_TEST_DECLARATION(x, y, DISABLED_)
+
+#define P2P_TEST_SET(x) \
+  P2P_TEST(x, OPEN) \
+  P2P_TEST(x, NAT_FULL_CONE) \
+  P2P_TEST(x, NAT_ADDR_RESTRICTED) \
+  P2P_TEST(x, NAT_PORT_RESTRICTED) \
+  P2P_TEST(x, NAT_SYMMETRIC) \
+  P2P_TEST(x, NAT_DOUBLE_CONE) \
+  P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
+  P2P_TEST(x, BLOCK_UDP) \
+  P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
+  P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
+  P2P_TEST(x, PROXY_HTTPS) \
+  P2P_TEST(x, PROXY_SOCKS)
+
+#define FLAKY_P2P_TEST_SET(x) \
+  P2P_TEST(x, OPEN) \
+  P2P_TEST(x, NAT_FULL_CONE) \
+  FLAKY_P2P_TEST(x, NAT_ADDR_RESTRICTED) \
+  P2P_TEST(x, NAT_PORT_RESTRICTED) \
+  P2P_TEST(x, NAT_SYMMETRIC) \
+  P2P_TEST(x, NAT_DOUBLE_CONE) \
+  P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
+  P2P_TEST(x, BLOCK_UDP) \
+  P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
+  P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
+  P2P_TEST(x, PROXY_HTTPS) \
+  P2P_TEST(x, PROXY_SOCKS)
+
+P2P_TEST_SET(OPEN)
+P2P_TEST_SET(NAT_FULL_CONE)
+FLAKY_P2P_TEST_SET(NAT_ADDR_RESTRICTED)
+FLAKY_P2P_TEST_SET(NAT_PORT_RESTRICTED)
+FLAKY_P2P_TEST_SET(NAT_SYMMETRIC)
+P2P_TEST_SET(NAT_DOUBLE_CONE)
+FLAKY_P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE)
+P2P_TEST_SET(BLOCK_UDP)
+P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP)
+P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP)
+P2P_TEST_SET(PROXY_HTTPS)
+P2P_TEST_SET(PROXY_SOCKS)
+
+// Test that a host behind NAT cannot be reached when incoming_only
+// is set to true.
+TEST_F(P2PTransportChannelTest, IncomingOnlyBlocked) {
+  ConfigureEndpoints(NAT_FULL_CONE, OPEN);
+
+  CreateChannels();
+  ch1()->set_incoming_only(true);
+
+  // Sleep for 1 second and verify that the channels are not connected.
+  talk_base::Thread::SleepMs(1000);
+
+  EXPECT_FALSE(ch1()->readable());
+  EXPECT_FALSE(ch1()->writable());
+  EXPECT_FALSE(ch2()->readable());
+  EXPECT_FALSE(ch2()->writable());
+
+  DestroyChannels();
+}
+
+// Test that a peer behind NAT can connect to a peer that has
+// incoming_only flag set.
+TEST_F(P2PTransportChannelTest, IncomingOnlyOpen) {
+  ConfigureEndpoints(OPEN, NAT_FULL_CONE);
+
+  CreateChannels();
+  ch1()->set_incoming_only(true);
+
+  EXPECT_TRUE_WAIT_MARGIN(ch1() != NULL && ch2() != NULL &&
+                          ch1()->readable() && ch1()->writable() &&
+                          ch2()->readable() && ch2()->writable(),
+                          1000, 1000);
+
+  DestroyChannels();
+}
+
+// Test what happens when we have 2 users behind the same NAT. This can lead
+// to interesting behavior because the STUN server will only give out the
+// address of the outermost NAT.
+class P2PTransportChannelSameNatTest : public P2PTransportChannelTestBase {
+ protected:
+  void ConfigureEndpoints(Config nat_type, Config config1, Config config2) {
+    ASSERT(nat_type >= NAT_FULL_CONE && nat_type <= NAT_SYMMETRIC);
+    talk_base::NATSocketServer::Translator* outer_nat =
+        nat()->AddTranslator(kPublicAddrs[0], kNatAddrs[0],
+            static_cast<talk_base::NATType>(nat_type - NAT_FULL_CONE));
+    ConfigureEndpoint(outer_nat, 0, config1);
+    ConfigureEndpoint(outer_nat, 1, config2);
+  }
+  void ConfigureEndpoint(talk_base::NATSocketServer::Translator* nat,
+                         int endpoint, Config config) {
+    ASSERT(config <= NAT_SYMMETRIC);
+    if (config == OPEN) {
+      AddAddress(endpoint, kPrivateAddrs[endpoint]);
+      nat->AddClient(kPrivateAddrs[endpoint]);
+    } else {
+      AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]);
+      nat->AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint],
+          static_cast<talk_base::NATType>(config - NAT_FULL_CONE))->AddClient(
+              kCascadedPrivateAddrs[endpoint]);
+    }
+  }
+};
+
+TEST_F(P2PTransportChannelSameNatTest, TestConesBehindSameCone) {
+  ConfigureEndpoints(NAT_FULL_CONE, NAT_FULL_CONE, NAT_FULL_CONE);
+  Test(kLocalUdpToLocalUdp);
+}
+
+// Test what happens when we have multiple available pathways.
+// In the future we will try different RTTs and configs for the different
+// interfaces, so that we can simulate a user with Ethernet and VPN networks.
+class P2PTransportChannelMultihomedTest : public P2PTransportChannelTestBase {
+};
+
+// Test that we can establish connectivity when both peers are multihomed.
+TEST_F(P2PTransportChannelMultihomedTest, TestBasic) {
+  AddAddress(0, kPublicAddrs[0]);
+  AddAddress(0, kAlternateAddrs[0]);
+  AddAddress(1, kPublicAddrs[1]);
+  AddAddress(1, kAlternateAddrs[1]);
+  Test(kLocalUdpToLocalUdp);
+}
+
+// Test that we can quickly switch links if an interface goes down.
+TEST_F(P2PTransportChannelMultihomedTest, TestFailover) {
+  AddAddress(0, kPublicAddrs[0]);
+  AddAddress(1, kPublicAddrs[1]);
+  AddAddress(1, kAlternateAddrs[1]);
+  // Use only local ports for simplicity.
+  SetAllocatorFlags(0, kOnlyLocalPorts);
+  SetAllocatorFlags(1, kOnlyLocalPorts);
+
+  // Create channels and let them go writable, as usual.
+  CreateChannels();
+  EXPECT_TRUE_WAIT(ch1()->readable() && ch1()->writable() &&
+                   ch2()->readable() && ch2()->writable(),
+                   1000);
+  EXPECT_TRUE(
+      ch1()->best_connection() && ch2()->best_connection() &&
+      LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+      RemoteCandidate(ch1())->address().EqualIPs(kPublicAddrs[1]));
+
+  // Blackhole any traffic to or from the public addrs.
+  LOG(LS_INFO) << "Failing over...";
+  fw()->AddRule(false, talk_base::FP_ANY, talk_base::FD_ANY,
+                kPublicAddrs[1]);
+
+  // We should detect loss of connectivity within 5 seconds or so.
+  EXPECT_TRUE_WAIT(!ch1()->writable(), 7000);
+
+  // We should switch over to use the alternate addr immediately
+  // when we lose writability.
+  EXPECT_TRUE_WAIT(
+      ch1()->best_connection() && ch2()->best_connection() &&
+      LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+      RemoteCandidate(ch1())->address().EqualIPs(kAlternateAddrs[1]),
+      3000);
+
+  DestroyChannels();
+}
+
+// Test that we can switch links in a coordinated fashion.
+TEST_F(P2PTransportChannelMultihomedTest, TestDrain) {
+  AddAddress(0, kPublicAddrs[0]);
+  AddAddress(1, kPublicAddrs[1]);
+  // Use only local ports for simplicity.
+  SetAllocatorFlags(0, kOnlyLocalPorts);
+  SetAllocatorFlags(1, kOnlyLocalPorts);
+
+  // Create channels and let them go writable, as usual.
+  CreateChannels();
+  EXPECT_TRUE_WAIT(ch1()->readable() && ch1()->writable() &&
+                   ch2()->readable() && ch2()->writable(),
+                   1000);
+  EXPECT_TRUE(
+      ch1()->best_connection() && ch2()->best_connection() &&
+      LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+      RemoteCandidate(ch1())->address().EqualIPs(kPublicAddrs[1]));
+
+  // Remove the public interface, add the alternate interface, and allocate
+  // a new generation of candidates for the new interface (via Connect()).
+  LOG(LS_INFO) << "Draining...";
+  AddAddress(1, kAlternateAddrs[1]);
+  RemoveAddress(1, kPublicAddrs[1]);
+  ch2()->Connect();
+
+  // We should switch over to use the alternate address after
+  // an exchange of pings.
+  EXPECT_TRUE_WAIT(
+      ch1()->best_connection() && ch2()->best_connection() &&
+      LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+      RemoteCandidate(ch1())->address().EqualIPs(kAlternateAddrs[1]),
+      3000);
+
+  DestroyChannels();
+}
diff --git a/talk/p2p/base/parsing.cc b/talk/p2p/base/parsing.cc
index a54d379..ebe0596 100644
--- a/talk/p2p/base/parsing.cc
+++ b/talk/p2p/base/parsing.cc
@@ -120,6 +120,14 @@
   }
 }
 
+void AddXmlAttrIfNonEmpty(buzz::XmlElement* elem,
+                          const buzz::QName name,
+                          const std::string& value) {
+  if (!value.empty()) {
+    elem->AddAttr(name, value);
+  }
+}
+
 void AddXmlChildren(buzz::XmlElement* parent,
                     const std::vector<buzz::XmlElement*>& children) {
   for (std::vector<buzz::XmlElement*>::const_iterator iter = children.begin();
diff --git a/talk/p2p/base/parsing.h b/talk/p2p/base/parsing.h
index 7931431..96f4e8b 100644
--- a/talk/p2p/base/parsing.h
+++ b/talk/p2p/base/parsing.h
@@ -112,6 +112,7 @@
 
 const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
                                     const std::string& name);
+
 bool RequireXmlChild(const buzz::XmlElement* parent,
                      const std::string& name,
                      const buzz::XmlElement** child,
@@ -120,6 +121,9 @@
                     const buzz::QName& name,
                     std::string* value,
                     ParseError* error);
+void AddXmlAttrIfNonEmpty(buzz::XmlElement* elem,
+                          const buzz::QName name,
+                          const std::string& value);
 void AddXmlChildren(buzz::XmlElement* parent,
                     const std::vector<buzz::XmlElement*>& children);
 void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest);
diff --git a/talk/p2p/base/port.cc b/talk/p2p/base/port.cc
index bb4ab26..d47e90e 100644
--- a/talk/p2p/base/port.cc
+++ b/talk/p2p/base/port.cc
@@ -126,7 +126,7 @@
 
 Port::Port(talk_base::Thread* thread, const std::string& type,
            talk_base::PacketSocketFactory* factory, talk_base::Network* network,
-           uint32 ip, int min_port, int max_port)
+           const talk_base::IPAddress& ip, int min_port, int max_port)
     : thread_(thread),
       factory_(factory),
       type_(type),
@@ -212,7 +212,7 @@
   } else if (!msg) {
     // STUN message handled already
   } else if (msg->type() == STUN_BINDING_REQUEST) {
-    SignalUnknownAddress(this, addr, msg, remote_username);
+    SignalUnknownAddress(this, addr, msg, remote_username, false);
   } else {
     // NOTE(tschmelcher): STUN_BINDING_RESPONSE is benign. It occurs if we
     // pruned a connection for this port while it had STUN requests in flight,
@@ -351,7 +351,7 @@
   StunAddressAttribute* addr_attr =
       StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
   addr_attr->SetPort(addr.port());
-  addr_attr->SetIP(addr.ip());
+  addr_attr->SetIP(addr.ipaddr());
   response.AddAttribute(addr_attr);
 
   // Send the response message.
@@ -528,11 +528,8 @@
 }
 
 const Candidate& Connection::local_candidate() const {
-  if (local_candidate_index_ < port_->candidates().size())
-    return port_->candidates()[local_candidate_index_];
-  ASSERT(false);
-  static Candidate foo;
-  return foo;
+  ASSERT(local_candidate_index_ < port_->candidates().size());
+  return port_->candidates()[local_candidate_index_];
 }
 
 void Connection::set_read_state(ReadState value) {
diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h
index 8304146..1e81b0a 100644
--- a/talk/p2p/base/port.h
+++ b/talk/p2p/base/port.h
@@ -77,7 +77,7 @@
  public:
   Port(talk_base::Thread* thread, const std::string& type,
        talk_base::PacketSocketFactory* factory, talk_base::Network* network,
-       uint32 ip, int min_port, int max_port);
+       const talk_base::IPAddress& ip, int min_port, int max_port);
   virtual ~Port();
 
   // The thread on which this port performs its I/O.
@@ -107,7 +107,6 @@
   const std::string& password() const { return password_; }
   void set_password(const std::string& password) { password_ = password; }
 
-
   // A value in [0,1] that indicates the preference for this port versus other
   // ports on this client.  (Larger indicates more preference.)
   float preference() const { return preference_; }
@@ -141,7 +140,8 @@
   const AddressMap& connections() { return connections_; }
 
   // Returns the connection to the given address or NULL if none exists.
-  Connection* GetConnection(const talk_base::SocketAddress& remote_addr);
+  virtual Connection* GetConnection(
+      const talk_base::SocketAddress& remote_addr);
 
   // Creates a new connection to the given address.
   enum CandidateOrigin { ORIGIN_THIS_PORT, ORIGIN_OTHER_PORT, ORIGIN_MESSAGE };
@@ -160,15 +160,15 @@
   // Indicates that we received a successful STUN binding request from an
   // address that doesn't correspond to any current connection.  To turn this
   // into a real connection, call CreateConnection.
-  sigslot::signal4<Port*, const talk_base::SocketAddress&, StunMessage*,
-                   const std::string&> SignalUnknownAddress;
+  sigslot::signal5<Port*, const talk_base::SocketAddress&, StunMessage*,
+                   const std::string&, bool> SignalUnknownAddress;
 
   // Sends a response message (normal or error) to the given request.  One of
   // these methods should be called as a response to SignalUnknownAddress.
   // NOTE: You MUST call CreateConnection BEFORE SendBindingResponse.
-  void SendBindingResponse(StunMessage* request,
-                           const talk_base::SocketAddress& addr);
-  void SendBindingErrorResponse(
+  virtual void SendBindingResponse(StunMessage* request,
+                                   const talk_base::SocketAddress& addr);
+  virtual void SendBindingErrorResponse(
       StunMessage* request, const talk_base::SocketAddress& addr,
       int error_code, const std::string& reason);
 
@@ -211,6 +211,9 @@
 
   // Debugging description of this port
   std::string ToString() const;
+  talk_base::IPAddress& ip() { return ip_; }
+  int min_port() { return min_port_; }
+  int max_port() { return max_port_; }
 
  protected:
   // Fills in the local address of the port.
@@ -241,7 +244,7 @@
   talk_base::PacketSocketFactory* factory_;
   std::string type_;
   talk_base::Network* network_;
-  uint32 ip_;
+  talk_base::IPAddress ip_;
   int min_port_;
   int max_port_;
   uint32 generation_;
diff --git a/talk/p2p/base/port_unittest.cc b/talk/p2p/base/port_unittest.cc
new file mode 100644
index 0000000..9a84606
--- /dev/null
+++ b/talk/p2p/base/port_unittest.cc
@@ -0,0 +1,761 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/natserver.h"
+#include "talk/base/natsocketfactory.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/tcpport.h"
+#include "talk/p2p/base/udpport.h"
+#include "talk/p2p/base/teststunserver.h"
+#include "talk/p2p/base/testrelayserver.h"
+
+using talk_base::AsyncPacketSocket;
+using talk_base::NATType;
+using talk_base::NAT_OPEN_CONE;
+using talk_base::NAT_ADDR_RESTRICTED;
+using talk_base::NAT_PORT_RESTRICTED;
+using talk_base::NAT_SYMMETRIC;
+using talk_base::PacketSocketFactory;
+using talk_base::scoped_ptr;
+using talk_base::Socket;
+using talk_base::SocketAddress;
+using namespace cricket;
+
+static const int kTimeout = 1000;
+static const SocketAddress kLocalAddr1 = SocketAddress("192.168.1.2", 0);
+static const SocketAddress kLocalAddr2 = SocketAddress("192.168.1.3", 0);
+static const SocketAddress kNatAddr1 = SocketAddress("77.77.77.77",
+                                                    talk_base::NAT_SERVER_PORT);
+static const SocketAddress kNatAddr2 = SocketAddress("88.88.88.88",
+                                                    talk_base::NAT_SERVER_PORT);
+static const SocketAddress kStunAddr = SocketAddress("99.99.99.1",
+                                                     STUN_SERVER_PORT);
+static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000);
+static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
+static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002);
+static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003);
+static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004);
+static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005);
+
+static Candidate GetCandidate(Port* port) {
+  assert(port->candidates().size() == 1);
+  return port->candidates()[0];
+}
+
+static SocketAddress GetAddress(Port* port) {
+  return GetCandidate(port).address();
+}
+
+class TestChannel : public sigslot::has_slots<> {
+ public:
+  TestChannel(Port* p1, Port* p2)
+      : src_(p1), dst_(p2), address_count_(0), conn_(NULL),
+        remote_request_(NULL) {
+    src_->SignalAddressReady.connect(this, &TestChannel::OnAddressReady);
+    src_->SignalUnknownAddress.connect(this, &TestChannel::OnUnknownAddress);
+  }
+
+  int address_count() { return address_count_; }
+  Connection* conn() { return conn_; }
+  const SocketAddress& remote_address() { return remote_address_; }
+  const std::string remote_fragment() { return remote_frag_; }
+
+  void Start() {
+    src_->PrepareAddress();
+  }
+  void CreateConnection() {
+    conn_ = src_->CreateConnection(GetCandidate(dst_), Port::ORIGIN_MESSAGE);
+  }
+  void AcceptConnection() {
+    ASSERT_TRUE(remote_request_ != NULL);
+    Candidate c = GetCandidate(dst_);
+    c.set_address(remote_address_);
+    conn_ = src_->CreateConnection(c, Port::ORIGIN_MESSAGE);
+    src_->SendBindingResponse(remote_request_, remote_address_);
+    delete remote_request_;
+  }
+  void Ping() {
+    conn_->Ping(0);
+  }
+  void Stop() {
+    conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed);
+    conn_->Destroy();
+  }
+
+  void OnAddressReady(Port* port) {
+    address_count_++;
+  }
+
+  void OnUnknownAddress(Port* port, const SocketAddress& addr,
+                        StunMessage* msg, const std::string& rf,
+                        bool /*port_muxed*/) {
+    ASSERT_EQ(src_.get(), port);
+    if (!remote_address_.IsAny()) {
+      ASSERT_EQ(remote_address_, addr);
+      delete remote_request_;
+    }
+    remote_address_ = addr;
+    remote_request_ = msg;
+    remote_frag_ = rf;
+  }
+
+  void OnDestroyed(Connection* conn) {
+    ASSERT_EQ(conn_, conn);
+    conn_ = NULL;
+  }
+
+ private:
+  talk_base::Thread* thread_;
+  talk_base::scoped_ptr<Port> src_;
+  Port* dst_;
+
+  int address_count_;
+  Connection* conn_;
+  SocketAddress remote_address_;
+  StunMessage* remote_request_;
+  std::string remote_frag_;
+};
+
+class PortTest : public testing::Test {
+ public:
+  PortTest()
+      : main_(talk_base::Thread::Current()),
+        pss_(new talk_base::PhysicalSocketServer),
+        ss_(new talk_base::VirtualSocketServer(pss_.get())),
+        ss_scope_(ss_.get()),
+        network_("unittest", "unittest", talk_base::IPAddress(INADDR_ANY)),
+        socket_factory_(talk_base::Thread::Current()),
+        nat_factory1_(ss_.get(), kNatAddr1),
+        nat_factory2_(ss_.get(), kNatAddr2),
+        nat_socket_factory1_(&nat_factory1_),
+        nat_socket_factory2_(&nat_factory2_),
+        stun_server_(main_, kStunAddr),
+        relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr,
+                      kRelayTcpIntAddr, kRelayTcpExtAddr,
+                      kRelaySslTcpIntAddr, kRelaySslTcpExtAddr) {
+  }
+
+ protected:
+  static void SetUpTestCase() {
+    // Ensure the RNG is inited.
+    talk_base::InitRandom(NULL, 0);
+  }
+
+  void TestLocalToLocal() {
+    UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+    UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+    TestConnectivity("udp", port1, "udp", port2, true, true, true, true);
+  }
+  void TestLocalToStun(NATType type) {
+    UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+    nat_server2_.reset(CreateNatServer(kNatAddr2, type));
+    StunPort* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
+    TestConnectivity("udp", port1, StunName(type), port2,
+                     type == NAT_OPEN_CONE, true, type != NAT_SYMMETRIC, true);
+  }
+  void TestLocalToRelay(ProtocolType proto) {
+    UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+    RelayPort* port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_UDP);
+    TestConnectivity("udp", port1, RelayName(proto), port2,
+                     true, true, true, true);
+  }
+  void TestStunToLocal(NATType type) {
+    nat_server1_.reset(CreateNatServer(kNatAddr1, type));
+    StunPort* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+    UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+    TestConnectivity(StunName(type), port1, "udp", port2,
+                     true, type != NAT_SYMMETRIC, true, true);
+  }
+  void TestStunToStun(NATType type1, NATType type2) {
+    nat_server1_.reset(CreateNatServer(kNatAddr1, type1));
+    StunPort* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+    nat_server2_.reset(CreateNatServer(kNatAddr2, type2));
+    StunPort* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
+    TestConnectivity(StunName(type1), port1, StunName(type2), port2,
+                     type2 == NAT_OPEN_CONE,
+                     type1 != NAT_SYMMETRIC, type2 != NAT_SYMMETRIC,
+                     type1 + type2 < (NAT_PORT_RESTRICTED + NAT_SYMMETRIC));
+  }
+  void TestStunToRelay(NATType type, ProtocolType proto) {
+    nat_server1_.reset(CreateNatServer(kNatAddr1, type));
+    StunPort* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+    RelayPort* port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_UDP);
+    TestConnectivity(StunName(type), port1, RelayName(proto), port2,
+                     true, type != NAT_SYMMETRIC, true, true);
+  }
+  void TestTcpToTcp() {
+    TCPPort* port1 = CreateTcpPort(kLocalAddr1);
+    TCPPort* port2 = CreateTcpPort(kLocalAddr2);
+    TestConnectivity("tcp", port1, "tcp", port2, true, false, true, true);
+  }
+  void TestTcpToRelay(ProtocolType proto) {
+    TCPPort* port1 = CreateTcpPort(kLocalAddr1);
+    RelayPort* port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_TCP);
+    TestConnectivity("tcp", port1, RelayName(proto), port2,
+                     true, false, true, true);
+  }
+  void TestSslTcpToRelay(ProtocolType proto) {
+    TCPPort* port1 = CreateTcpPort(kLocalAddr1);
+    RelayPort* port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_SSLTCP);
+    TestConnectivity("ssltcp", port1, RelayName(proto), port2,
+                     true, false, true, true);
+  }
+
+  // helpers for above functions
+  UDPPort* CreateUdpPort(const SocketAddress& addr) {
+    return CreateUdpPort(addr, &socket_factory_);
+  }
+  UDPPort* CreateUdpPort(const SocketAddress& addr,
+                         PacketSocketFactory* socket_factory) {
+    return UDPPort::Create(main_, socket_factory, &network_,
+                           addr.ipaddr(), 0, 0);
+  }
+  TCPPort* CreateTcpPort(const SocketAddress& addr) {
+    return CreateTcpPort(addr, &socket_factory_);
+  }
+  TCPPort* CreateTcpPort(const SocketAddress& addr,
+                         PacketSocketFactory* socket_factory) {
+    return TCPPort::Create(main_, socket_factory, &network_,
+                           addr.ipaddr(), 0, 0, true);
+  }
+  StunPort* CreateStunPort(const SocketAddress& addr,
+                           talk_base::PacketSocketFactory* factory) {
+    return StunPort::Create(main_, factory, &network_,
+                            addr.ipaddr(), 0, 0, kStunAddr);
+  }
+  RelayPort* CreateRelayPort(const SocketAddress& addr,
+                             ProtocolType int_proto, ProtocolType ext_proto) {
+    std::string user = talk_base::CreateRandomString(16);
+    std::string pass = talk_base::CreateRandomString(16);
+    RelayPort* port = RelayPort::Create(main_, &socket_factory_, &network_,
+                                        addr.ipaddr(), 0, 0, user, pass, "");
+    SocketAddress addrs[] =
+        { kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr };
+    port->AddServerAddress(ProtocolAddress(addrs[int_proto], int_proto));
+    // TODO: Add an external address for ext_proto, so that the
+    // other side can connect to this port using a non-UDP protocol.
+    return port;
+  }
+  talk_base::NATServer* CreateNatServer(const SocketAddress& addr,
+                                        talk_base::NATType type) {
+    return new talk_base::NATServer(type, ss_.get(), addr, ss_.get(), addr);
+  }
+  static const char* StunName(NATType type) {
+    switch (type) {
+      case NAT_OPEN_CONE:       return "stun(open cone)";
+      case NAT_ADDR_RESTRICTED: return "stun(addr restricted)";
+      case NAT_PORT_RESTRICTED: return "stun(port restricted)";
+      case NAT_SYMMETRIC:       return "stun(symmetric)";
+      default:                  return "stun(?)";
+    }
+  }
+  static const char* RelayName(ProtocolType type) {
+    switch (type) {
+      case PROTO_UDP:           return "relay(udp)";
+      case PROTO_TCP:           return "relay(tcp)";
+      case PROTO_SSLTCP:        return "relay(ssltcp)";
+      default:                  return "relay(?)";
+    }
+  }
+
+  // this does all the work
+  void TestConnectivity(const char* name1, Port* port1,
+                        const char* name2, Port* port2,
+                        bool accept, bool same_addr1,
+                        bool same_addr2, bool possible);
+
+ private:
+  talk_base::Thread* main_;
+  talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
+  talk_base::scoped_ptr<talk_base::VirtualSocketServer> ss_;
+  talk_base::SocketServerScope ss_scope_;
+  talk_base::Network network_;
+  talk_base::BasicPacketSocketFactory socket_factory_;
+  talk_base::scoped_ptr<talk_base::NATServer> nat_server1_;
+  talk_base::scoped_ptr<talk_base::NATServer> nat_server2_;
+  talk_base::NATSocketFactory nat_factory1_;
+  talk_base::NATSocketFactory nat_factory2_;
+  talk_base::BasicPacketSocketFactory nat_socket_factory1_;
+  talk_base::BasicPacketSocketFactory nat_socket_factory2_;
+  TestStunServer stun_server_;
+  TestRelayServer relay_server_;
+};
+
+void PortTest::TestConnectivity(const char* name1, Port* port1,
+                                const char* name2, Port* port2,
+                                bool accept, bool same_addr1,
+                                bool same_addr2, bool possible) {
+  LOG(LS_INFO) << "Test: " << name1 << " to " << name2 << ": ";
+  port1->set_name("src");
+  port2->set_name("dst");
+
+  // Set up channels.
+  TestChannel ch1(port1, port2);
+  TestChannel ch2(port2, port1);
+  EXPECT_EQ(0, ch1.address_count());
+  EXPECT_EQ(0, ch2.address_count());
+
+  // Acquire addresses.
+  ch1.Start();
+  ch2.Start();
+  ASSERT_EQ_WAIT(1, ch1.address_count(), kTimeout);
+  ASSERT_EQ_WAIT(1, ch2.address_count(), kTimeout);
+
+  // Send a ping from src to dst. This may or may not make it.
+  ch1.CreateConnection();
+  ASSERT_TRUE(ch1.conn() != NULL);
+  EXPECT_TRUE_WAIT(ch1.conn()->connected(), kTimeout);  // for TCP connect
+  ch1.Ping();
+  WAIT(!ch2.remote_address().IsAny(), kTimeout);
+
+  if (accept) {
+    // We are able to send a ping from src to dst. This is the case when
+    // sending to UDP ports and cone NATs.
+    EXPECT_TRUE(ch1.remote_address().IsAny());
+    EXPECT_EQ(ch2.remote_fragment(), port1->username_fragment());
+
+    // Ensure the ping came from the same address used for src.
+    // This is the case unless the source NAT was symmetric.
+    if (same_addr1) EXPECT_EQ(ch2.remote_address(), GetAddress(port1));
+    EXPECT_TRUE(same_addr2);
+
+    // Send a ping from dst to src.
+    ch2.AcceptConnection();
+    ASSERT_TRUE(ch2.conn() != NULL);
+    ch2.Ping();
+    EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2.conn()->write_state(),
+                   kTimeout);
+  } else {
+    // We can't send a ping from src to dst, so flip it around. This will happen
+    // when the destination NAT is addr/port restricted or symmetric.
+    EXPECT_TRUE(ch1.remote_address().IsAny());
+    EXPECT_TRUE(ch2.remote_address().IsAny());
+
+    // Send a ping from dst to src. Again, this may or may not make it.
+    ch2.CreateConnection();
+    ASSERT_TRUE(ch2.conn() != NULL);
+    ch2.Ping();
+    WAIT(ch2.conn()->write_state() == Connection::STATE_WRITABLE, kTimeout);
+
+    if (same_addr1 && same_addr2) {
+      // The new ping got back to the source.
+      EXPECT_EQ(Connection::STATE_READABLE, ch1.conn()->read_state());
+      EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+
+      // First connection may not be writable if the first ping did not get
+      // through.  So we will have to do another.
+      if (ch1.conn()->write_state() == Connection::STATE_WRITE_CONNECT) {
+        ch1.Ping();
+        EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+                       kTimeout);
+      }
+    } else if (!same_addr1 && possible) {
+      // The new ping went to the candidate address, but that address was bad.
+      // This will happen when the source NAT is symmetric.
+      EXPECT_TRUE(ch1.remote_address().IsAny());
+      EXPECT_TRUE(ch2.remote_address().IsAny());
+
+      // However, since we have now sent a ping to the source IP, we should be
+      // able to get a ping from it. This gives us the real source address.
+      ch1.Ping();
+      EXPECT_TRUE_WAIT(!ch2.remote_address().IsAny(), kTimeout);
+      EXPECT_EQ(Connection::STATE_READ_TIMEOUT, ch2.conn()->read_state());
+      EXPECT_TRUE(ch1.remote_address().IsAny());
+
+      // Pick up the actual address and establish the connection.
+      ch2.AcceptConnection();
+      ASSERT_TRUE(ch2.conn() != NULL);
+      ch2.Ping();
+      EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2.conn()->write_state(),
+                     kTimeout);
+    } else if (!same_addr2 && possible) {
+      // The new ping came in, but from an unexpected address. This will happen
+      // when the destination NAT is symmetric.
+      EXPECT_FALSE(ch1.remote_address().IsAny());
+      EXPECT_EQ(Connection::STATE_READ_TIMEOUT, ch1.conn()->read_state());
+
+      // Update our address and complete the connection.
+      ch1.AcceptConnection();
+      ch1.Ping();
+      EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+                     kTimeout);
+    } else {  // (!possible)
+      // There should be s no way for the pings to reach each other. Check it.
+      EXPECT_TRUE(ch1.remote_address().IsAny());
+      EXPECT_TRUE(ch2.remote_address().IsAny());
+      ch1.Ping();
+      WAIT(!ch2.remote_address().IsAny(), kTimeout);
+      EXPECT_TRUE(ch1.remote_address().IsAny());
+      EXPECT_TRUE(ch2.remote_address().IsAny());
+    }
+  }
+
+  // Everything should be good, unless we know the situation is impossible.
+  ASSERT_TRUE(ch1.conn() != NULL);
+  ASSERT_TRUE(ch2.conn() != NULL);
+  if (possible) {
+    EXPECT_EQ(Connection::STATE_READABLE, ch1.conn()->read_state());
+    EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+    EXPECT_EQ(Connection::STATE_READABLE, ch2.conn()->read_state());
+    EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+  } else {
+    EXPECT_NE(Connection::STATE_READABLE, ch1.conn()->read_state());
+    EXPECT_NE(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+    EXPECT_NE(Connection::STATE_READABLE, ch2.conn()->read_state());
+    EXPECT_NE(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+  }
+
+  // Tear down and ensure that goes smoothly.
+  ch1.Stop();
+  ch2.Stop();
+  EXPECT_TRUE_WAIT(ch1.conn() == NULL, kTimeout);
+  EXPECT_TRUE_WAIT(ch2.conn() == NULL, kTimeout);
+}
+
+class FakePacketSocketFactory : public talk_base::PacketSocketFactory {
+ public:
+  FakePacketSocketFactory()
+      : next_udp_socket_(NULL),
+        next_server_tcp_socket_(NULL),
+        next_client_tcp_socket_(NULL) {
+  }
+  virtual ~FakePacketSocketFactory() { }
+
+  virtual AsyncPacketSocket* CreateUdpSocket(
+      const SocketAddress& address, int min_port, int max_port) {
+    EXPECT_TRUE(next_udp_socket_ != NULL);
+    AsyncPacketSocket* result = next_udp_socket_;
+    next_udp_socket_ = NULL;
+    return result;
+  }
+
+  virtual AsyncPacketSocket* CreateServerTcpSocket(
+      const SocketAddress& local_address, int min_port, int max_port,
+      bool ssl) {
+    EXPECT_TRUE(next_server_tcp_socket_ != NULL);
+    AsyncPacketSocket* result = next_server_tcp_socket_;
+    next_server_tcp_socket_ = NULL;
+    return result;
+  }
+
+  // TODO: |proxy_info| and |user_agent| should be set
+  // per-factory and not when socket is created.
+  virtual AsyncPacketSocket* CreateClientTcpSocket(
+      const SocketAddress& local_address, const SocketAddress& remote_address,
+      const talk_base::ProxyInfo& proxy_info,
+      const std::string& user_agent, bool ssl) {
+    EXPECT_TRUE(next_client_tcp_socket_ != NULL);
+    AsyncPacketSocket* result = next_client_tcp_socket_;
+    next_client_tcp_socket_ = NULL;
+    return result;
+  }
+
+  void set_next_udp_socket(AsyncPacketSocket* next_udp_socket) {
+    next_udp_socket_ = next_udp_socket;
+  }
+  void set_next_server_tcp_socket(AsyncPacketSocket* next_server_tcp_socket) {
+    next_server_tcp_socket_ = next_server_tcp_socket;
+  }
+  void set_next_client_tcp_socket(AsyncPacketSocket* next_client_tcp_socket) {
+    next_client_tcp_socket_ = next_client_tcp_socket;
+  }
+
+ private:
+  AsyncPacketSocket* next_udp_socket_;
+  AsyncPacketSocket* next_server_tcp_socket_;
+  AsyncPacketSocket* next_client_tcp_socket_;
+};
+
+class FakeAsyncPacketSocket : public AsyncPacketSocket {
+ public:
+  // Returns current local address. Address may be set to NULL if the
+  // socket is not bound yet (GetState() returns STATE_BINDING).
+  virtual SocketAddress GetLocalAddress() const {
+    return SocketAddress();
+  }
+
+  // Returns remote address. Returns zeroes if this is not a client TCP socket.
+  virtual SocketAddress GetRemoteAddress() const {
+    return SocketAddress();
+  }
+
+  // Send a packet.
+  virtual int Send(const void *pv, size_t cb) {
+    return cb;
+  }
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) {
+    return cb;
+  }
+  virtual int Close() {
+    return 0;
+  }
+
+  virtual State GetState() const { return state_; }
+  virtual int GetOption(Socket::Option opt, int* value) { return 0; }
+  virtual int SetOption(Socket::Option opt, int value) { return 0; }
+  virtual int GetError() const { return 0; }
+  virtual void SetError(int error) { }
+
+  void set_state(State state) { state_ = state; }
+
+ private:
+  State state_;
+};
+
+// Local -> XXXX
+TEST_F(PortTest, TestLocalToLocal) {
+  TestLocalToLocal();
+}
+
+TEST_F(PortTest, TestLocalToConeNat) {
+  TestLocalToStun(NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestLocalToARNat) {
+  TestLocalToStun(NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestLocalToPRNat) {
+  TestLocalToStun(NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestLocalToSymNat) {
+  TestLocalToStun(NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestLocalToRelay) {
+  TestLocalToRelay(PROTO_UDP);
+}
+
+TEST_F(PortTest, TestLocalToTcpRelay) {
+  TestLocalToRelay(PROTO_TCP);
+}
+
+TEST_F(PortTest, TestLocalToSslTcpRelay) {
+  TestLocalToRelay(PROTO_SSLTCP);
+}
+
+// Cone NAT -> XXXX
+TEST_F(PortTest, TestConeNatToLocal) {
+  TestStunToLocal(NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestConeNatToConeNat) {
+  TestStunToStun(NAT_OPEN_CONE, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestConeNatToARNat) {
+  TestStunToStun(NAT_OPEN_CONE, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestConeNatToPRNat) {
+  TestStunToStun(NAT_OPEN_CONE, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestConeNatToSymNat) {
+  TestStunToStun(NAT_OPEN_CONE, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestConeNatToRelay) {
+  TestStunToRelay(NAT_OPEN_CONE, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestConeNatToTcpRelay) {
+  TestStunToRelay(NAT_OPEN_CONE, PROTO_TCP);
+}
+
+// Address-restricted NAT -> XXXX
+TEST_F(PortTest, TestARNatToLocal) {
+  TestStunToLocal(NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToConeNat) {
+  TestStunToStun(NAT_ADDR_RESTRICTED, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestARNatToARNat) {
+  TestStunToStun(NAT_ADDR_RESTRICTED, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToPRNat) {
+  TestStunToStun(NAT_ADDR_RESTRICTED, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToSymNat) {
+  TestStunToStun(NAT_ADDR_RESTRICTED, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestARNatToRelay) {
+  TestStunToRelay(NAT_ADDR_RESTRICTED, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestARNATNatToTcpRelay) {
+  TestStunToRelay(NAT_ADDR_RESTRICTED, PROTO_TCP);
+}
+
+// Port-restricted NAT -> XXXX
+TEST_F(PortTest, TestPRNatToLocal) {
+  TestStunToLocal(NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToConeNat) {
+  TestStunToStun(NAT_PORT_RESTRICTED, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestPRNatToARNat) {
+  TestStunToStun(NAT_PORT_RESTRICTED, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToPRNat) {
+  TestStunToStun(NAT_PORT_RESTRICTED, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToSymNat) {
+  // Will "fail"
+  TestStunToStun(NAT_PORT_RESTRICTED, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestPRNatToRelay) {
+  TestStunToRelay(NAT_PORT_RESTRICTED, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestPRNatToTcpRelay) {
+  TestStunToRelay(NAT_PORT_RESTRICTED, PROTO_TCP);
+}
+
+// Symmetric NAT -> XXXX
+TEST_F(PortTest, TestSymNatToLocal) {
+  TestStunToLocal(NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestSymNatToConeNat) {
+  TestStunToStun(NAT_SYMMETRIC, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestSymNatToARNat) {
+  TestStunToStun(NAT_SYMMETRIC, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestSymNatToPRNat) {
+  // Will "fail"
+  TestStunToStun(NAT_SYMMETRIC, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestSymNatToSymNat) {
+  // Will "fail"
+  TestStunToStun(NAT_SYMMETRIC, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestSymNatToRelay) {
+  TestStunToRelay(NAT_SYMMETRIC, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestSymNatToTcpRelay) {
+  TestStunToRelay(NAT_SYMMETRIC, PROTO_TCP);
+}
+
+// Outbound TCP -> XXXX
+TEST_F(PortTest, TestTcpToTcp) {
+  TestTcpToTcp();
+}
+
+/* TODO: Enable these once testrelayserver can accept external TCP.
+TEST_F(PortTest, TestTcpToTcpRelay) {
+  TestTcpToRelay(PROTO_TCP);
+}
+
+TEST_F(PortTest, TestTcpToSslTcpRelay) {
+  TestTcpToRelay(PROTO_SSLTCP);
+}
+*/
+
+// Outbound SSLTCP -> XXXX
+/* TODO: Enable these once testrelayserver can accept external SSL.
+TEST_F(PortTest, TestSslTcpToTcpRelay) {
+  TestSslTcpToRelay(PROTO_TCP);
+}
+
+TEST_F(PortTest, TestSslTcpToSslTcpRelay) {
+  TestSslTcpToRelay(PROTO_SSLTCP);
+}
+*/
+
+TEST_F(PortTest, TestTcpNoDelay) {
+  TCPPort* port1 = CreateTcpPort(kLocalAddr1);
+  int option_value = -1;
+  int success = port1->GetOption(talk_base::Socket::OPT_NODELAY,
+                                 &option_value);
+  ASSERT_EQ(0, success);  // GetOption() should complete successfully w/ 0
+  ASSERT_EQ(1, option_value);
+  delete port1;
+}
+
+TEST_F(PortTest, TestDelayedBindingUdp) {
+  FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket();
+  FakePacketSocketFactory socket_factory;
+
+  socket_factory.set_next_udp_socket(socket);
+  scoped_ptr<UDPPort> port(
+      CreateUdpPort(kLocalAddr1, &socket_factory));
+
+  socket->set_state(AsyncPacketSocket::STATE_BINDING);
+  port->PrepareAddress();
+
+  EXPECT_EQ(0U, port->candidates().size());
+  socket->SignalAddressReady(socket, kLocalAddr2);
+
+  EXPECT_EQ(1U, port->candidates().size());
+}
+
+TEST_F(PortTest, TestDelayedBindingTcp) {
+  FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket();
+  FakePacketSocketFactory socket_factory;
+
+  socket_factory.set_next_server_tcp_socket(socket);
+  scoped_ptr<TCPPort> port(
+      CreateTcpPort(kLocalAddr1, &socket_factory));
+
+  socket->set_state(AsyncPacketSocket::STATE_BINDING);
+  port->PrepareAddress();
+
+  EXPECT_EQ(0U, port->candidates().size());
+  socket->SignalAddressReady(socket, kLocalAddr2);
+
+  EXPECT_EQ(1U, port->candidates().size());
+}
diff --git a/talk/p2p/base/portallocator.cc b/talk/p2p/base/portallocator.cc
new file mode 100644
index 0000000..974117f
--- /dev/null
+++ b/talk/p2p/base/portallocator.cc
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/portallocator.h"
+
+#include "talk/p2p/base/portallocatorsessionproxy.h"
+
+namespace cricket {
+
+PortAllocator::~PortAllocator() {
+  for (SessionMuxerMap::iterator iter = muxers_.begin();
+       iter != muxers_.end(); ++iter) {
+    delete iter->second;
+  }
+}
+
+PortAllocatorSession* PortAllocator::CreateSession(
+    const std::string& sid,
+    const std::string& name,
+    const std::string& session_type) {
+  if (flags_ & PORTALLOCATOR_ENABLE_BUNDLE) {
+    PortAllocatorSessionMuxer* muxer = GetSessionMuxer(sid);
+    if (!muxer) {
+      PortAllocatorSession* session_impl = CreateSession(name, session_type);
+      // Create PortAllocatorSessionMuxer object for |session_impl|.
+      muxer = new PortAllocatorSessionMuxer(session_impl);
+      muxer->SignalDestroyed.connect(
+          this, &PortAllocator::OnSessionMuxerDestroyed);
+      // Add PortAllocatorSession to the map.
+      muxers_[sid] = muxer;
+    }
+    PortAllocatorSessionProxy* proxy =
+        new PortAllocatorSessionProxy(name, session_type, flags_);
+    muxer->RegisterSessionProxy(proxy);
+    return proxy;
+  }
+  return CreateSession(name, session_type);
+}
+
+PortAllocatorSessionMuxer* PortAllocator::GetSessionMuxer(
+    const std::string& sid) const {
+  SessionMuxerMap::const_iterator iter = muxers_.find(sid);
+  if (iter != muxers_.end())
+    return iter->second;
+  return NULL;
+}
+
+void PortAllocator::OnSessionMuxerDestroyed(
+    PortAllocatorSessionMuxer* session) {
+  SessionMuxerMap::iterator iter;
+  for (iter = muxers_.begin(); iter != muxers_.end(); ++iter) {
+    if (iter->second == session)
+      break;
+  }
+  if (iter != muxers_.end())
+    muxers_.erase(iter);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/portallocator.h b/talk/p2p/base/portallocator.h
index 175bdbc..79a08c1 100644
--- a/talk/p2p/base/portallocator.h
+++ b/talk/p2p/base/portallocator.h
@@ -47,18 +47,31 @@
 const uint32 PORTALLOCATOR_DISABLE_RELAY = 0x04;
 const uint32 PORTALLOCATOR_DISABLE_TCP = 0x08;
 const uint32 PORTALLOCATOR_ENABLE_SHAKER = 0x10;
+const uint32 PORTALLOCATOR_ENABLE_BUNDLE = 0x20;
 
 const uint32 kDefaultPortAllocatorFlags = 0;
 
+class PortAllocatorSessionMuxer;
+
 class PortAllocatorSession : public sigslot::has_slots<> {
  public:
-  explicit PortAllocatorSession(uint32 flags) : flags_(flags) {}
+  // TODO Remove session_type argument (and other places), as
+  // its not used.
+  explicit PortAllocatorSession(const std::string& name,
+                                const std::string& session_type,
+                                uint32 flags) :
+      name_(name),
+      session_type_(session_type),
+      flags_(flags) {
+  }
 
   // Subclasses should clean up any ports created.
   virtual ~PortAllocatorSession() {}
 
   uint32 flags() const { return flags_; }
   void set_flags(uint32 flags) { flags_ = flags; }
+  const std::string& name() const { return name_; }
+  const std::string& session_type() const { return session_type_; }
 
   // Prepares an initial set of ports to try.
   virtual void GetInitialPorts() = 0;
@@ -74,23 +87,33 @@
 
   uint32 generation() { return generation_; }
   void set_generation(uint32 generation) { generation_ = generation; }
+  sigslot::signal1<PortAllocatorSession*> SignalDestroyed;
+
+ protected:
+  std::string name_;
+  std::string session_type_;
 
  private:
   uint32 flags_;
   uint32 generation_;
 };
 
-class PortAllocator {
+class PortAllocator : public sigslot::has_slots<> {
  public:
   PortAllocator() :
       flags_(kDefaultPortAllocatorFlags),
       min_port_(0),
       max_port_(0) {
   }
-  virtual ~PortAllocator() {}
+  virtual ~PortAllocator();
 
-  virtual PortAllocatorSession *CreateSession(const std::string &name,
-      const std::string &session_type) = 0;
+  PortAllocatorSession* CreateSession(
+      const std::string& sid,
+      const std::string& name,
+      const std::string& session_type);
+
+  PortAllocatorSessionMuxer* GetSessionMuxer(const std::string& sid) const;
+  void OnSessionMuxerDestroyed(PortAllocatorSessionMuxer* session);
 
   uint32 flags() const { return flags_; }
   void set_flags(uint32 flags) { flags_ = flags; }
@@ -116,11 +139,18 @@
   }
 
  protected:
+  virtual PortAllocatorSession* CreateSession(const std::string &name,
+      const std::string &session_type) = 0;
+
+  typedef std::map<std::string, PortAllocatorSessionMuxer*> SessionMuxerMap;
+
   uint32 flags_;
   std::string agent_;
   talk_base::ProxyInfo proxy_;
   int min_port_;
   int max_port_;
+
+  SessionMuxerMap muxers_;
 };
 
 }  // namespace cricket
diff --git a/talk/p2p/base/portallocatorsessionproxy.cc b/talk/p2p/base/portallocatorsessionproxy.cc
new file mode 100644
index 0000000..8305cd3
--- /dev/null
+++ b/talk/p2p/base/portallocatorsessionproxy.cc
@@ -0,0 +1,154 @@
+/*
+ * 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/p2p/base/portallocatorsessionproxy.h"
+
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/portproxy.h"
+
+namespace cricket {
+
+PortAllocatorSessionMuxer::PortAllocatorSessionMuxer(
+    PortAllocatorSession* session)
+    : session_(session) {
+  session_->SignalPortReady.connect(
+      this, &PortAllocatorSessionMuxer::OnPortReady);
+}
+
+PortAllocatorSessionMuxer::~PortAllocatorSessionMuxer() {
+  for (size_t i = 0; i < session_proxies_.size(); ++i)
+    delete session_proxies_[i];
+
+  SignalDestroyed(this);
+}
+
+void PortAllocatorSessionMuxer::RegisterSessionProxy(
+    PortAllocatorSessionProxy* session_proxy) {
+  session_proxies_.push_back(session_proxy);
+  session_proxy->SignalDestroyed.connect(
+      this, &PortAllocatorSessionMuxer::OnSessionProxyDestroyed);
+  session_proxy->set_impl(session_.get());
+}
+
+void PortAllocatorSessionMuxer::OnPortReady(PortAllocatorSession* session,
+                                            Port* port) {
+  ASSERT(session == session_.get());
+  ports_.push_back(port);
+  port->SignalDestroyed.connect(
+      this, &PortAllocatorSessionMuxer::OnPortDestroyed);
+}
+
+void PortAllocatorSessionMuxer::OnPortDestroyed(Port* port) {
+  std::vector<Port*>::iterator it =
+      std::find(ports_.begin(), ports_.end(), port);
+  if (it != ports_.end())
+    ports_.erase(it);
+}
+
+void PortAllocatorSessionMuxer::OnSessionProxyDestroyed(
+    PortAllocatorSession* proxy) {
+  std::vector<PortAllocatorSessionProxy*>::iterator it =
+      std::find(session_proxies_.begin(), session_proxies_.end(), proxy);
+  if (it != session_proxies_.end())
+    session_proxies_.erase(it);
+
+  if (session_proxies_.empty()) {
+    // Destroy PortAllocatorSession and its associated muxer object if all
+    // proxies belonging to this session are already destroyed.
+    delete this;
+  }
+}
+
+PortAllocatorSessionProxy::~PortAllocatorSessionProxy() {
+  std::map<Port*, PortProxy*>::iterator it;
+  for (it = proxy_ports_.begin(); it != proxy_ports_.end(); it++)
+    delete it->second;
+
+  SignalDestroyed(this);
+}
+
+void PortAllocatorSessionProxy::set_impl(
+    PortAllocatorSession* session) {
+  impl_ = session;
+
+  impl_->SignalCandidatesReady.connect(
+      this, &PortAllocatorSessionProxy::OnCandidatesReady);
+  impl_->SignalPortReady.connect(
+      this, &PortAllocatorSessionProxy::OnPortReady);
+}
+
+void PortAllocatorSessionProxy::GetInitialPorts() {
+  ASSERT(impl_ != NULL);
+  impl_->GetInitialPorts();
+}
+
+void PortAllocatorSessionProxy::StartGetAllPorts() {
+  ASSERT(impl_ != NULL);
+  impl_->StartGetAllPorts();
+}
+
+void PortAllocatorSessionProxy::StopGetAllPorts() {
+  ASSERT(impl_ != NULL);
+  impl_->StartGetAllPorts();
+}
+
+bool PortAllocatorSessionProxy::IsGettingAllPorts() {
+  ASSERT(impl_ != NULL);
+  return impl_->IsGettingAllPorts();
+}
+
+void PortAllocatorSessionProxy::OnPortReady(PortAllocatorSession* session,
+                                            Port* port) {
+  ASSERT(session == impl_);
+
+  PortProxy* proxy_port = new PortProxy(
+      port->thread(), port->type(), port->socket_factory(), port->network(),
+      port->ip(), port->min_port(), port->max_port());
+  proxy_port->set_impl(port);
+  proxy_ports_[port] = proxy_port;
+  SignalPortReady(this, proxy_port);
+}
+
+void PortAllocatorSessionProxy::OnCandidatesReady(
+    PortAllocatorSession* session,
+    const std::vector<Candidate>& candidates) {
+  ASSERT(session == impl_);
+
+  // Since all proxy sessions share a common PortAllocatorSession,
+  // all Candidates will have name associated with the common PAS.
+  // Change Candidate name with the PortAllocatorSessionProxy name.
+  std::vector<Candidate> our_candidates;
+  for (size_t i = 0; i < candidates.size(); ++i) {
+    Candidate new_local_candidate = candidates[i];
+    new_local_candidate.set_name(name_);
+    our_candidates.push_back(new_local_candidate);
+  }
+
+  SignalCandidatesReady(this, our_candidates);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/portallocatorsessionproxy.h b/talk/p2p/base/portallocatorsessionproxy.h
new file mode 100644
index 0000000..5f9eaec
--- /dev/null
+++ b/talk/p2p/base/portallocatorsessionproxy.h
@@ -0,0 +1,101 @@
+/*
+ * 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_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
+#define TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
+
+#include <string>
+
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/portallocator.h"
+
+namespace cricket {
+class PortAllocator;
+class PortAllocatorSessionProxy;
+class PortProxy;
+
+// This class maintains the list of cricket::Port* objects. Ports will be
+// deleted upon receiving SignalDestroyed signal. This class is used when
+// PORTALLOCATOR_ENABLE_BUNDLE flag is set.
+
+class PortAllocatorSessionMuxer : public sigslot::has_slots<> {
+ public:
+  explicit PortAllocatorSessionMuxer(PortAllocatorSession* session);
+  virtual ~PortAllocatorSessionMuxer();
+
+  void RegisterSessionProxy(PortAllocatorSessionProxy* session_proxy);
+
+  void OnPortReady(PortAllocatorSession* session, Port* port);
+  void OnPortDestroyed(Port* port);
+
+  const std::vector<Port*>& ports() { return ports_; }
+
+  sigslot::signal1<PortAllocatorSessionMuxer*> SignalDestroyed;
+
+ private:
+  void OnSessionProxyDestroyed(PortAllocatorSession* proxy);
+
+  // Port will be deleted when SignalDestroyed received, otherwise delete
+  // happens when PortAllocatorSession dtor is called.
+  std::vector<Port*> ports_;
+  talk_base::scoped_ptr<PortAllocatorSession> session_;
+  std::vector<PortAllocatorSessionProxy*> session_proxies_;
+};
+
+class PortAllocatorSessionProxy : public PortAllocatorSession {
+ public:
+  PortAllocatorSessionProxy(const std::string& name,
+                            const std::string& session_type,
+                            uint32 flags)
+      : PortAllocatorSession(name, session_type, flags),
+        impl_(NULL) {}
+
+  virtual ~PortAllocatorSessionProxy();
+
+  PortAllocatorSession* impl() { return impl_; }
+  void set_impl(PortAllocatorSession* session);
+
+  // Forwards call to the actual PortAllocatorSession.
+  virtual void GetInitialPorts();
+  virtual void StartGetAllPorts();
+  virtual void StopGetAllPorts();
+  virtual bool IsGettingAllPorts();
+
+ private:
+  void OnPortReady(PortAllocatorSession* session, Port* port);
+  void OnCandidatesReady(PortAllocatorSession* session,
+                         const std::vector<Candidate>& candidates);
+  void OnPortDestroyed(Port* port);
+
+  // This is the actual PortAllocatorSession, owned by PortAllocator.
+  PortAllocatorSession* impl_;
+  std::map<Port*, PortProxy*> proxy_ports_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
diff --git a/talk/p2p/base/portproxy.cc b/talk/p2p/base/portproxy.cc
new file mode 100644
index 0000000..6c20127
--- /dev/null
+++ b/talk/p2p/base/portproxy.cc
@@ -0,0 +1,85 @@
+/*
+ * 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/p2p/base/portproxy.h"
+
+namespace cricket {
+
+void PortProxy::set_impl(Port* port) {
+  impl_ = port;
+  impl_->SignalUnknownAddress.connect(
+      this, &PortProxy::OnUnknownAddress);
+  impl_->SignalDestroyed.connect(this, &PortProxy::OnPortDestroyed);
+}
+
+void PortProxy::PrepareAddress() {
+  impl_->PrepareAddress();
+}
+
+Connection* PortProxy::CreateConnection(const Candidate& remote_candidate,
+                                        CandidateOrigin origin) {
+  ASSERT(impl_ != NULL);
+  return impl_->CreateConnection(remote_candidate, origin);
+}
+
+int PortProxy::SendTo(const void* data,
+                      size_t size,
+                      const talk_base::SocketAddress& addr,
+                      bool payload) {
+  ASSERT(impl_ != NULL);
+  return impl_->SendTo(data, size, addr, payload);
+}
+
+int PortProxy::SetOption(talk_base::Socket::Option opt,
+                         int value) {
+  ASSERT(impl_ != NULL);
+  return impl_->SetOption(opt, value);
+}
+
+int PortProxy::GetError() {
+  ASSERT(impl_ != NULL);
+  return impl_->GetError();
+}
+
+void PortProxy::OnUnknownAddress(
+    Port *port,
+    const talk_base::SocketAddress &addr,
+    StunMessage *stun_msg,
+    const std::string &remote_username,
+    bool port_muxed) {
+  ASSERT(port == impl_);
+  ASSERT(!port_muxed);
+  SignalUnknownAddress(this, addr, stun_msg, remote_username, true);
+}
+
+void PortProxy::OnPortDestroyed(Port* port) {
+  ASSERT(port == impl_);
+  // |port| will be destroyed in PortAllocatorSessionMuxer.
+  SignalDestroyed(this);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/portproxy.h b/talk/p2p/base/portproxy.h
new file mode 100644
index 0000000..49707d4
--- /dev/null
+++ b/talk/p2p/base/portproxy.h
@@ -0,0 +1,85 @@
+/*
+ * 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_P2P_BASE_PORTPROXY_H_
+#define TALK_P2P_BASE_PORTPROXY_H_
+
+#include "talk/p2p/base/port.h"
+
+namespace cricket {
+
+class PortProxy : public Port {
+ public:
+  PortProxy(talk_base::Thread* thread, const std::string& type,
+            talk_base::PacketSocketFactory* factory,
+            talk_base::Network* network,
+            const talk_base::IPAddress& ip, int min_port, int max_port)
+      : Port(thread, type, factory, network, ip, min_port, max_port) {
+  }
+  virtual ~PortProxy() {}
+
+  Port* impl() { return impl_; }
+  void set_impl(Port* port);
+
+  // Forwards call to the actual Port.
+  virtual void PrepareAddress();
+  virtual Connection* CreateConnection(const Candidate& remote_candidate,
+    CandidateOrigin origin);
+  virtual int SendTo(
+      const void* data, size_t size, const talk_base::SocketAddress& addr,
+      bool payload);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+
+  virtual void SendBindingResponse(StunMessage* request,
+                           const talk_base::SocketAddress& addr) {
+    impl_->SendBindingResponse(request, addr);
+  }
+
+  virtual Connection* GetConnection(
+      const talk_base::SocketAddress& remote_addr) {
+    return impl_->GetConnection(remote_addr);
+  }
+
+  virtual void SendBindingErrorResponse(
+        StunMessage* request, const talk_base::SocketAddress& addr,
+        int error_code, const std::string& reason) {
+    impl_->SendBindingErrorResponse(request, addr, error_code, reason);
+  }
+
+ private:
+  void OnUnknownAddress(Port *port, const talk_base::SocketAddress &addr,
+                        StunMessage *stun_msg,
+                        const std::string &remote_username,
+                        bool port_muxed);
+  void OnPortDestroyed(Port* port);
+  Port* impl_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORTPROXY_H_
diff --git a/talk/p2p/base/pseudotcp.cc b/talk/p2p/base/pseudotcp.cc
index fc903e0..99a1f43 100644
--- a/talk/p2p/base/pseudotcp.cc
+++ b/talk/p2p/base/pseudotcp.cc
@@ -38,7 +38,7 @@
 #include "talk/base/logging.h"
 #include "talk/base/socket.h"
 #include "talk/base/stringutils.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 
 // The following logging is for detailed (packet-level) analysis only.
 #define _DBG_NONE     0
diff --git a/talk/p2p/base/pseudotcp_unittest.cc b/talk/p2p/base/pseudotcp_unittest.cc
new file mode 100644
index 0000000..7bf5874
--- /dev/null
+++ b/talk/p2p/base/pseudotcp_unittest.cc
@@ -0,0 +1,845 @@
+/*
+ * 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 <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/stream.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/p2p/base/pseudotcp.h"
+
+using cricket::PseudoTcp;
+
+static const int kConnectTimeoutMs = 10000;  // ~3 * default RTO of 3000ms
+static const int kTransferTimeoutMs = 15000;
+static const int kBlockSize = 4096;
+
+class PseudoTcpForTest : public cricket::PseudoTcp {
+ public:
+  PseudoTcpForTest(cricket::IPseudoTcpNotify* notify, uint32 conv)
+      : PseudoTcp(notify, conv) {
+  }
+
+  bool isReceiveBufferFull() const {
+    return PseudoTcp::isReceiveBufferFull();
+  }
+
+  void disableWindowScale() {
+    PseudoTcp::disableWindowScale();
+  }
+};
+
+class PseudoTcpTestBase : public testing::Test,
+                      public talk_base::MessageHandler,
+                      public cricket::IPseudoTcpNotify {
+ public:
+  PseudoTcpTestBase()
+      : local_(this, 1),
+        remote_(this, 1),
+        have_connected_(false),
+        have_disconnected_(false),
+        local_mtu_(65535),
+        remote_mtu_(65535),
+        delay_(0),
+        loss_(0) {
+    // Set use of the test RNG to get predictable loss patterns.
+    talk_base::SetRandomTestMode(true);
+  }
+  ~PseudoTcpTestBase() {
+    // Put it back for the next test.
+    talk_base::SetRandomTestMode(false);
+  }
+  void SetLocalMtu(int mtu) {
+    local_.NotifyMTU(mtu);
+    local_mtu_ = mtu;
+  }
+  void SetRemoteMtu(int mtu) {
+    remote_.NotifyMTU(mtu);
+    remote_mtu_ = mtu;
+  }
+  void SetDelay(int delay) {
+    delay_ = delay;
+  }
+  void SetLoss(int percent) {
+    loss_ = percent;
+  }
+  void SetOptNagling(bool enable_nagles) {
+    local_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles);
+    remote_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles);
+  }
+  void SetOptAckDelay(int ack_delay) {
+    local_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay);
+    remote_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay);
+  }
+  void SetOptSndBuf(int size) {
+    local_.SetOption(PseudoTcp::OPT_SNDBUF, size);
+    remote_.SetOption(PseudoTcp::OPT_SNDBUF, size);
+  }
+  void SetRemoteOptRcvBuf(int size) {
+    remote_.SetOption(PseudoTcp::OPT_RCVBUF, size);
+  }
+  void SetLocalOptRcvBuf(int size) {
+    local_.SetOption(PseudoTcp::OPT_RCVBUF, size);
+  }
+  void DisableRemoteWindowScale() {
+    remote_.disableWindowScale();
+  }
+  void DisableLocalWindowScale() {
+    local_.disableWindowScale();
+  }
+
+ protected:
+  int Connect() {
+    int ret = local_.Connect();
+    if (ret == 0) {
+      UpdateLocalClock();
+    }
+    return ret;
+  }
+  void Close() {
+    local_.Close(false);
+    UpdateLocalClock();
+  }
+
+  enum { MSG_LPACKET, MSG_RPACKET, MSG_LCLOCK, MSG_RCLOCK, MSG_IOCOMPLETE,
+         MSG_WRITE};
+  virtual void OnTcpOpen(PseudoTcp* tcp) {
+    // Consider ourselves connected when the local side gets OnTcpOpen.
+    // OnTcpWriteable isn't fired at open, so we trigger it now.
+    LOG(LS_VERBOSE) << "Opened";
+    if (tcp == &local_) {
+      have_connected_ = true;
+      OnTcpWriteable(tcp);
+    }
+  }
+  // Test derived from the base should override
+  //   virtual void OnTcpReadable(PseudoTcp* tcp)
+  // and
+  //   virtual void OnTcpWritable(PseudoTcp* tcp)
+  virtual void OnTcpClosed(PseudoTcp* tcp, uint32 error) {
+    // Consider ourselves closed when the remote side gets OnTcpClosed.
+    // TODO: OnTcpClosed is only ever notified in case of error in
+    // the current implementation.  Solicited close is not (yet) supported.
+    LOG(LS_VERBOSE) << "Closed";
+    EXPECT_EQ(0U, error);
+    if (tcp == &remote_) {
+      have_disconnected_ = true;
+    }
+  }
+  virtual WriteResult TcpWritePacket(PseudoTcp* tcp,
+                                     const char* buffer, size_t len) {
+    // Randomly drop the desired percentage of packets.
+    // Also drop packets that are larger than the configured MTU.
+    if (talk_base::CreateRandomId() % 100 < static_cast<uint32>(loss_)) {
+      LOG(LS_VERBOSE) << "Randomly dropping packet, size=" << len;
+    } else if (len > static_cast<size_t>(
+        talk_base::_min(local_mtu_, remote_mtu_))) {
+      LOG(LS_VERBOSE) << "Dropping packet that exceeds path MTU, size=" << len;
+    } else {
+      int id = (tcp == &local_) ? MSG_RPACKET : MSG_LPACKET;
+      std::string packet(buffer, len);
+      talk_base::Thread::Current()->PostDelayed(delay_, this, id,
+          talk_base::WrapMessageData(packet));
+    }
+    return WR_SUCCESS;
+  }
+
+  void UpdateLocalClock() { UpdateClock(&local_, MSG_LCLOCK); }
+  void UpdateRemoteClock() { UpdateClock(&remote_, MSG_RCLOCK); }
+  void UpdateClock(PseudoTcp* tcp, uint32 message) {
+    long interval;  // NOLINT
+    tcp->GetNextClock(PseudoTcp::Now(), interval);
+    interval = talk_base::_max<int>(interval, 0L);  // sometimes interval is < 0
+    talk_base::Thread::Current()->Clear(this, message);
+    talk_base::Thread::Current()->PostDelayed(interval, this, message);
+  }
+
+  virtual void OnMessage(talk_base::Message* message) {
+    switch (message->message_id) {
+      case MSG_LPACKET: {
+        const std::string& s(
+            talk_base::UseMessageData<std::string>(message->pdata));
+        local_.NotifyPacket(s.c_str(), s.size());
+        UpdateLocalClock();
+        break;
+      }
+      case MSG_RPACKET: {
+        const std::string& s(
+            talk_base::UseMessageData<std::string>(message->pdata));
+        remote_.NotifyPacket(s.c_str(), s.size());
+        UpdateRemoteClock();
+        break;
+      }
+      case MSG_LCLOCK:
+        local_.NotifyClock(PseudoTcp::Now());
+        UpdateLocalClock();
+        break;
+      case MSG_RCLOCK:
+        remote_.NotifyClock(PseudoTcp::Now());
+        UpdateRemoteClock();
+        break;
+      default:
+        break;
+    }
+    delete message->pdata;
+  }
+
+  PseudoTcpForTest local_;
+  PseudoTcpForTest remote_;
+  talk_base::MemoryStream send_stream_;
+  talk_base::MemoryStream recv_stream_;
+  bool have_connected_;
+  bool have_disconnected_;
+  int local_mtu_;
+  int remote_mtu_;
+  int delay_;
+  int loss_;
+};
+
+class PseudoTcpTest : public PseudoTcpTestBase {
+ public:
+  void TestTransfer(int size) {
+    uint32 start, elapsed;
+    size_t received;
+    // Create some dummy data to send.
+    send_stream_.ReserveSize(size);
+    for (int i = 0; i < size; ++i) {
+      char ch = static_cast<char>(i);
+      send_stream_.Write(&ch, 1, NULL, NULL);
+    }
+    send_stream_.Rewind();
+    // Prepare the receive stream.
+    recv_stream_.ReserveSize(size);
+    // Connect and wait until connected.
+    start = talk_base::Time();
+    EXPECT_EQ(0, Connect());
+    EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+    // Sending will start from OnTcpWriteable and complete when all data has
+    // been received.
+    EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+    elapsed = talk_base::TimeSince(start);
+    recv_stream_.GetSize(&received);
+    // Ensure we closed down OK and we got the right data.
+    // TODO: Ensure the errors are cleared properly.
+    //EXPECT_EQ(0, local_.GetError());
+    //EXPECT_EQ(0, remote_.GetError());
+    EXPECT_EQ(static_cast<size_t>(size), received);
+    EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(),
+                        recv_stream_.GetBuffer(), size));
+    LOG(LS_INFO) << "Transferred " << received << " bytes in " << elapsed
+                 << " ms (" << size * 8 / elapsed << " Kbps)";
+  }
+
+ private:
+  // IPseudoTcpNotify interface
+
+  virtual void OnTcpReadable(PseudoTcp* tcp) {
+    // Stream bytes to the recv stream as they arrive.
+    if (tcp == &remote_) {
+      ReadData();
+
+      // TODO: OnTcpClosed() is currently only notified on error -
+      // there is no on-the-wire equivalent of TCP FIN.
+      // So we fake the notification when all the data has been read.
+      size_t received, required;
+      recv_stream_.GetPosition(&received);
+      send_stream_.GetSize(&required);
+      if (received == required)
+        OnTcpClosed(&remote_, 0);
+    }
+  }
+  virtual void OnTcpWriteable(PseudoTcp* tcp) {
+    // Write bytes from the send stream when we can.
+    // Shut down when we've sent everything.
+    if (tcp == &local_) {
+      LOG(LS_VERBOSE) << "Flow Control Lifted";
+      bool done;
+      WriteData(&done);
+      if (done) {
+        Close();
+      }
+    }
+  }
+
+  void ReadData() {
+    char block[kBlockSize];
+    size_t position;
+    int rcvd;
+    do {
+      rcvd = remote_.Recv(block, sizeof(block));
+      if (rcvd != -1) {
+        recv_stream_.Write(block, rcvd, NULL, NULL);
+        recv_stream_.GetPosition(&position);
+        LOG(LS_VERBOSE) << "Received: " << position;
+      }
+    } while (rcvd > 0);
+  }
+  void WriteData(bool* done) {
+    size_t position, tosend;
+    int sent;
+    char block[kBlockSize];
+    do {
+      send_stream_.GetPosition(&position);
+      if (send_stream_.Read(block, sizeof(block), &tosend, NULL) !=
+          talk_base::SR_EOS) {
+        sent = local_.Send(block, tosend);
+        UpdateLocalClock();
+        if (sent != -1) {
+          send_stream_.SetPosition(position + sent);
+          LOG(LS_VERBOSE) << "Sent: " << position + sent;
+        } else {
+          send_stream_.SetPosition(position);
+          LOG(LS_VERBOSE) << "Flow Controlled";
+        }
+      } else {
+        sent = tosend = 0;
+      }
+    } while (sent > 0);
+    *done = (tosend == 0);
+  }
+
+ private:
+  talk_base::MemoryStream send_stream_;
+  talk_base::MemoryStream recv_stream_;
+};
+
+
+class PseudoTcpTestPingPong : public PseudoTcpTestBase {
+ public:
+  PseudoTcpTestPingPong()
+      : iterations_remaining_(0),
+	sender_(NULL),
+	receiver_(NULL),
+	bytes_per_send_(0) {
+  }
+  void SetBytesPerSend(int bytes) {
+    bytes_per_send_ = bytes;
+  }
+  void TestPingPong(int size, int iterations) {
+    uint32 start, elapsed;
+    iterations_remaining_ = iterations;
+    receiver_ = &remote_;
+    sender_ = &local_;
+    // Create some dummy data to send.
+    send_stream_.ReserveSize(size);
+    for (int i = 0; i < size; ++i) {
+      char ch = static_cast<char>(i);
+      send_stream_.Write(&ch, 1, NULL, NULL);
+    }
+    send_stream_.Rewind();
+    // Prepare the receive stream.
+    recv_stream_.ReserveSize(size);
+    // Connect and wait until connected.
+    start = talk_base::Time();
+    EXPECT_EQ(0, Connect());
+    EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+    // Sending will start from OnTcpWriteable and stop when the required
+    // number of iterations have completed.
+    EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+    elapsed = talk_base::TimeSince(start);
+    LOG(LS_INFO) << "Performed " << iterations << " pings in "
+                 << elapsed << " ms";
+  }
+
+ private:
+  // IPseudoTcpNotify interface
+
+  virtual void OnTcpReadable(PseudoTcp* tcp) {
+    if (tcp != receiver_) {
+      LOG_F(LS_ERROR) << "unexpected OnTcpReadable";
+      return;
+    }
+    // Stream bytes to the recv stream as they arrive.
+    ReadData();
+    // If we've received the desired amount of data, rewind things
+    // and send it back the other way!
+    size_t position, desired;
+    recv_stream_.GetPosition(&position);
+    send_stream_.GetSize(&desired);
+    if (position == desired) {
+      if (receiver_ == &local_ && --iterations_remaining_ == 0) {
+        Close();
+        // TODO: Fake OnTcpClosed() on the receiver for now.
+        OnTcpClosed(&remote_, 0);
+        return;
+      }
+      PseudoTcp* tmp = receiver_;
+      receiver_ = sender_;
+      sender_ = tmp;
+      recv_stream_.Rewind();
+      send_stream_.Rewind();
+      OnTcpWriteable(sender_);
+    }
+  }
+  virtual void OnTcpWriteable(PseudoTcp* tcp) {
+    if (tcp != sender_)
+      return;
+    // Write bytes from the send stream when we can.
+    // Shut down when we've sent everything.
+    LOG(LS_VERBOSE) << "Flow Control Lifted";
+    WriteData();
+  }
+
+  void ReadData() {
+    char block[kBlockSize];
+    size_t position;
+    int rcvd;
+    do {
+      rcvd = receiver_->Recv(block, sizeof(block));
+      if (rcvd != -1) {
+        recv_stream_.Write(block, rcvd, NULL, NULL);
+        recv_stream_.GetPosition(&position);
+        LOG(LS_VERBOSE) << "Received: " << position;
+      }
+    } while (rcvd > 0);
+  }
+  void WriteData() {
+    size_t position, tosend;
+    int sent;
+    char block[kBlockSize];
+    do {
+      send_stream_.GetPosition(&position);
+      tosend = bytes_per_send_ ? bytes_per_send_ : sizeof(block);
+      if (send_stream_.Read(block, tosend, &tosend, NULL) !=
+          talk_base::SR_EOS) {
+        sent = sender_->Send(block, tosend);
+        UpdateLocalClock();
+        if (sent != -1) {
+          send_stream_.SetPosition(position + sent);
+          LOG(LS_VERBOSE) << "Sent: " << position + sent;
+        } else {
+          send_stream_.SetPosition(position);
+          LOG(LS_VERBOSE) << "Flow Controlled";
+        }
+      } else {
+        sent = tosend = 0;
+      }
+    } while (sent > 0);
+  }
+
+ private:
+  int iterations_remaining_;
+  PseudoTcp* sender_;
+  PseudoTcp* receiver_;
+  int bytes_per_send_;
+};
+
+// Fill the receiver window until it is full, drain it and then
+// fill it with the same amount. This is to test that receiver window
+// contracts and enlarges correctly.
+class PseudoTcpTestReceiveWindow : public PseudoTcpTestBase {
+ public:
+  // Not all the data are transfered, |size| just need to be big enough
+  // to fill up the receiver window twice.
+  void TestTransfer(int size) {
+    // Create some dummy data to send.
+    send_stream_.ReserveSize(size);
+    for (int i = 0; i < size; ++i) {
+      char ch = static_cast<char>(i);
+      send_stream_.Write(&ch, 1, NULL, NULL);
+    }
+    send_stream_.Rewind();
+
+    // Prepare the receive stream.
+    recv_stream_.ReserveSize(size);
+
+    // Connect and wait until connected.
+    EXPECT_EQ(0, Connect());
+    EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+
+    talk_base::Thread::Current()->Post(this, MSG_WRITE);
+    EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+
+    ASSERT_EQ(2u, send_position_.size());
+    ASSERT_EQ(2u, recv_position_.size());
+
+    const size_t estimated_recv_window = recv_position_[0];
+
+    // The difference in consecutive send positions should equal the
+    // receive window size or match very closely. This verifies that receive
+    // window is open after receiver drained all the data.
+    const size_t send_position_diff = send_position_[1] - send_position_[0];
+    EXPECT_GE(1024u, estimated_recv_window - send_position_diff);
+
+    // Receiver drained the receive window twice.
+    EXPECT_EQ(2 * estimated_recv_window, recv_position_[1]);
+  }
+
+  virtual void OnMessage(talk_base::Message* message) {
+    int message_id = message->message_id;
+    PseudoTcpTestBase::OnMessage(message);
+
+    switch (message_id) {
+      case MSG_WRITE: {
+        WriteData();
+        break;
+      }
+      default:
+        break;
+    }
+  }
+
+  uint32 EstimateReceiveWindowSize() const {
+    return recv_position_[0];
+  }
+
+  uint32 EstimateSendWindowSize() const {
+    return send_position_[0] - recv_position_[0];
+  }
+
+ private:
+  // IPseudoTcpNotify interface
+  virtual void OnTcpReadable(PseudoTcp* tcp) {
+  }
+
+  virtual void OnTcpWriteable(PseudoTcp* tcp) {
+  }
+
+  void ReadUntilIOPending() {
+    char block[kBlockSize];
+    size_t position;
+    int rcvd;
+
+    do {
+      rcvd = remote_.Recv(block, sizeof(block));
+      if (rcvd != -1) {
+        recv_stream_.Write(block, rcvd, NULL, NULL);
+        recv_stream_.GetPosition(&position);
+        LOG(LS_VERBOSE) << "Received: " << position;
+      }
+    } while (rcvd > 0);
+
+    recv_stream_.GetPosition(&position);
+    recv_position_.push_back(position);
+
+    // Disconnect if we have done two transfers.
+    if (recv_position_.size() == 2u) {
+      Close();
+      OnTcpClosed(&remote_, 0);
+    } else {
+      WriteData();
+    }
+  }
+
+  void WriteData() {
+    size_t position, tosend;
+    int sent;
+    char block[kBlockSize];
+    do {
+      send_stream_.GetPosition(&position);
+      if (send_stream_.Read(block, sizeof(block), &tosend, NULL) !=
+          talk_base::SR_EOS) {
+        sent = local_.Send(block, tosend);
+        UpdateLocalClock();
+        if (sent != -1) {
+          send_stream_.SetPosition(position + sent);
+          LOG(LS_VERBOSE) << "Sent: " << position + sent;
+        } else {
+          send_stream_.SetPosition(position);
+          LOG(LS_VERBOSE) << "Flow Controlled";
+        }
+      } else {
+        sent = tosend = 0;
+      }
+    } while (sent > 0);
+
+    if (remote_.isReceiveBufferFull()) {
+      send_stream_.GetPosition(&position);
+      send_position_.push_back(position);
+
+      // Drain the receiver buffer.
+      ReadUntilIOPending();
+    } else {
+      // If the receiver side is not full then keep writing.
+      talk_base::Thread::Current()->PostDelayed(10, this, MSG_WRITE);
+    }
+  }
+
+ private:
+  talk_base::MemoryStream send_stream_;
+  talk_base::MemoryStream recv_stream_;
+
+  std::vector<size_t> send_position_;
+  std::vector<size_t> recv_position_;
+};
+
+// Basic end-to-end data transfer tests
+
+// Test the normal case of sending data from one side to the other.
+TEST_F(PseudoTcpTest, TestSend) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  TestTransfer(1000000);
+}
+
+// Test sending data with a 50 ms RTT. Transmission should take longer due
+// to a slower ramp-up in send rate.
+TEST_F(PseudoTcpTest, TestSendWithDelay) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetDelay(50);
+  TestTransfer(1000000);
+}
+
+// Test sending data with packet loss. Transmission should take much longer due
+// to send back-off when loss occurs.
+TEST_F(PseudoTcpTest, TestSendWithLoss) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetLoss(10);
+  TestTransfer(100000);  // less data so test runs faster
+}
+
+// Test sending data with a 50 ms RTT and 10% packet loss. Transmission should
+// take much longer due to send back-off and slower detection of loss.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndLoss) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetDelay(50);
+  SetLoss(10);
+  TestTransfer(100000);  // less data so test runs faster
+}
+
+// Test sending data with 10% packet loss and Nagling disabled.  Transmission
+// should take about the same time as with Nagling enabled.
+TEST_F(PseudoTcpTest, TestSendWithLossAndOptNaglingOff) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetLoss(10);
+  SetOptNagling(false);
+  TestTransfer(100000);  // less data so test runs faster
+}
+
+// Test sending data with 10% packet loss and Delayed ACK disabled.
+// Transmission should be slightly faster than with it enabled.
+TEST_F(PseudoTcpTest, TestSendWithLossAndOptAckDelayOff) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetLoss(10);
+  SetOptAckDelay(0);
+  TestTransfer(100000);
+}
+
+// Test sending data with 50ms delay and Nagling disabled.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndOptNaglingOff) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetDelay(50);
+  SetOptNagling(false);
+  TestTransfer(100000);  // less data so test runs faster
+}
+
+// Test sending data with 50ms delay and Delayed ACK disabled.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndOptAckDelayOff) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetDelay(50);
+  SetOptAckDelay(0);
+  TestTransfer(100000);  // less data so test runs faster
+}
+
+// Test a large receive buffer with a sender that doesn't support scaling.
+TEST_F(PseudoTcpTest, TestSendRemoteNoWindowScale) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetLocalOptRcvBuf(100000);
+  DisableRemoteWindowScale();
+  TestTransfer(1000000);
+}
+
+// Test a large sender-side receive buffer with a receiver that doesn't support
+// scaling.
+TEST_F(PseudoTcpTest, TestSendLocalNoWindowScale) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetRemoteOptRcvBuf(100000);
+  DisableLocalWindowScale();
+  TestTransfer(1000000);
+}
+
+// Test when both sides use window scaling.
+TEST_F(PseudoTcpTest, TestSendBothUseWindowScale) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetRemoteOptRcvBuf(100000);
+  SetLocalOptRcvBuf(100000);
+  TestTransfer(1000000);
+}
+
+// Test using a large window scale value.
+TEST_F(PseudoTcpTest, TestSendLargeInFlight) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetRemoteOptRcvBuf(100000);
+  SetLocalOptRcvBuf(100000);
+  SetOptSndBuf(150000);
+  TestTransfer(1000000);
+}
+
+TEST_F(PseudoTcpTest, TestSendBothUseLargeWindowScale) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetRemoteOptRcvBuf(1000000);
+  SetLocalOptRcvBuf(1000000);
+  TestTransfer(10000000);
+}
+
+// Test using a small receive buffer.
+TEST_F(PseudoTcpTest, TestSendSmallReceiveBuffer) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetRemoteOptRcvBuf(10000);
+  SetLocalOptRcvBuf(10000);
+  TestTransfer(1000000);
+}
+
+// Test using a very small receive buffer.
+TEST_F(PseudoTcpTest, TestSendVerySmallReceiveBuffer) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetRemoteOptRcvBuf(100);
+  SetLocalOptRcvBuf(100);
+  TestTransfer(100000);
+}
+
+// Ping-pong (request/response) tests
+
+// Test sending <= 1x MTU of data in each ping/pong.  Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong1xMtu) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  TestPingPong(100, 100);
+}
+
+// Test sending 2x-3x MTU of data in each ping/pong.  Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong3xMtu) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  TestPingPong(400, 100);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong.
+// Should take ~1s, due to interaction between Nagling and Delayed ACK.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtu) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  TestPingPong(2000, 5);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong with Delayed ACK off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithAckDelayOff) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetOptAckDelay(0);
+  TestPingPong(2000, 100);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong with Nagling off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithNaglingOff) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetOptNagling(false);
+  TestPingPong(2000, 5);
+}
+
+// Test sending a ping as pair of short (non-full) segments.
+// Should take ~1s, due to Delayed ACK interaction with Nagling.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegments) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetOptAckDelay(5000);
+  SetBytesPerSend(50); // i.e. two Send calls per payload
+  TestPingPong(100, 5);
+}
+
+// Test sending ping as a pair of short (non-full) segments, with Nagling off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithNaglingOff) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetOptNagling(false);
+  SetBytesPerSend(50); // i.e. two Send calls per payload
+  TestPingPong(100, 5);
+}
+
+// Test sending <= 1x MTU of data ping/pong, in two segments, no Delayed ACK.
+// Should take ~1s.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithAckDelayOff) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetBytesPerSend(50); // i.e. two Send calls per payload
+  SetOptAckDelay(0);
+  TestPingPong(100, 5);
+}
+
+// Test that receive window expands and contract correctly.
+TEST_F(PseudoTcpTestReceiveWindow, TestReceiveWindow) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetOptNagling(false);
+  SetOptAckDelay(0);
+  TestTransfer(1024 * 1000);
+}
+
+// Test setting send window size to a very small value.
+TEST_F(PseudoTcpTestReceiveWindow, TestSetVerySmallSendWindowSize) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetOptNagling(false);
+  SetOptAckDelay(0);
+  SetOptSndBuf(900);
+  TestTransfer(1024 * 1000);
+  EXPECT_EQ(900u, EstimateSendWindowSize());
+}
+
+// Test setting receive window size to a value other than default.
+TEST_F(PseudoTcpTestReceiveWindow, TestSetReceiveWindowSize) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1500);
+  SetOptNagling(false);
+  SetOptAckDelay(0);
+  SetRemoteOptRcvBuf(100000);
+  SetLocalOptRcvBuf(100000);
+  TestTransfer(1024 * 1000);
+  EXPECT_EQ(100000u, EstimateReceiveWindowSize());
+}
+
+/* Test sending data with mismatched MTUs. We should detect this and reduce
+// our packet size accordingly.
+// TODO: This doesn't actually work right now. The current code
+// doesn't detect if the MTU is set too high on either side.
+TEST_F(PseudoTcpTest, TestSendWithMismatchedMtus) {
+  SetLocalMtu(1500);
+  SetRemoteMtu(1280);
+  TestTransfer(1000000);
+}
+*/
diff --git a/talk/p2p/base/rawtransportchannel.cc b/talk/p2p/base/rawtransportchannel.cc
index 8736053..0c2691c 100644
--- a/talk/p2p/base/rawtransportchannel.cc
+++ b/talk/p2p/base/rawtransportchannel.cc
@@ -95,7 +95,8 @@
 
 void RawTransportChannel::Connect() {
   // Create an allocator that only returns stun and relay ports.
-  allocator_session_ = allocator_->CreateSession(name(), content_type());
+  allocator_session_ = allocator_->CreateSession(
+      session_id(), name(), content_type());
 
   uint32 flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP;
 
diff --git a/talk/p2p/base/relayport.cc b/talk/p2p/base/relayport.cc
index 76bd5ff..c6c066a 100644
--- a/talk/p2p/base/relayport.cc
+++ b/talk/p2p/base/relayport.cc
@@ -187,9 +187,9 @@
 
 RelayPort::RelayPort(
     talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
-    talk_base::Network* network, uint32 ip, int min_port, int max_port,
-    const std::string& username, const std::string& password,
-    const std::string& magic_cookie)
+    talk_base::Network* network, const talk_base::IPAddress& ip,
+    int min_port, int max_port, const std::string& username,
+    const std::string& password, const std::string& magic_cookie)
     : Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port),
       ready_(false),
       magic_cookie_(magic_cookie),
@@ -554,7 +554,7 @@
 
   StunAddressAttribute* addr_attr =
       StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
-  addr_attr->SetIP(addr.ip());
+  addr_attr->SetIP(addr.ipaddr());
   addr_attr->SetPort(addr.port());
   request.AddAttribute(addr_attr);
 
@@ -702,7 +702,7 @@
     return;
   }
 
-  talk_base::SocketAddress remote_addr2(addr_attr->ip(), addr_attr->port());
+  talk_base::SocketAddress remote_addr2(addr_attr->ipaddr(), addr_attr->port());
 
   const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA);
   if (!data_attr) {
@@ -764,7 +764,7 @@
   } else if (addr_attr->family() != 1) {
     LOG(INFO) << "Mapped address has bad family";
   } else {
-    talk_base::SocketAddress addr(addr_attr->ip(), addr_attr->port());
+    talk_base::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
     entry_->OnConnect(addr, connection_);
   }
 
diff --git a/talk/p2p/base/relayport.h b/talk/p2p/base/relayport.h
index 62bb758..2137606 100644
--- a/talk/p2p/base/relayport.h
+++ b/talk/p2p/base/relayport.h
@@ -55,9 +55,9 @@
   // RelayPort doesn't yet do anything fancy in the ctor.
   static RelayPort* Create(
       talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
-      talk_base::Network* network, uint32 ip, int min_port, int max_port,
-      const std::string& username, const std::string& password,
-      const std::string& magic_cookie) {
+      talk_base::Network* network, const talk_base::IPAddress& ip,
+      int min_port, int max_port, const std::string& username,
+      const std::string& password, const std::string& magic_cookie) {
     return new RelayPort(thread, factory, network, ip, min_port, max_port,
                          username, password, magic_cookie);
   }
@@ -85,9 +85,9 @@
 
  protected:
   RelayPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
-            talk_base::Network*, uint32 ip, int min_port, int max_port,
-            const std::string& username, const std::string& password,
-            const std::string& magic_cookie);
+            talk_base::Network*, const talk_base::IPAddress& ip,
+            int min_port, int max_port, const std::string& username,
+            const std::string& password, const std::string& magic_cookie);
   bool Init();
 
   void SetReady();
diff --git a/talk/p2p/base/relayport_unittest.cc b/talk/p2p/base/relayport_unittest.cc
new file mode 100644
index 0000000..688e635
--- /dev/null
+++ b/talk/p2p/base/relayport_unittest.cc
@@ -0,0 +1,292 @@
+/*
+ * libjingle
+ * Copyright 2009 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/logging.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/relayserver.h"
+
+using talk_base::SocketAddress;
+
+static const SocketAddress kLocalAddress = SocketAddress("192.168.1.2", 0);
+static const SocketAddress kRelayUdpAddr = SocketAddress("99.99.99.1", 5000);
+static const SocketAddress kRelayTcpAddr = SocketAddress("99.99.99.2", 5001);
+static const SocketAddress kRelaySslAddr = SocketAddress("99.99.99.3", 443);
+static const SocketAddress kRelayExtAddr = SocketAddress("99.99.99.3", 5002);
+
+static const int kTimeoutMs = 1000;
+static const int kMaxTimeoutMs = 5000;
+
+// Tests connecting a RelayPort to a fake relay server
+// (cricket::RelayServer) using all currently available protocols. The
+// network layer is faked out by using a VirtualSocketServer for
+// creating sockets. The test will monitor the current state of the
+// RelayPort and created sockets by listening for signals such as,
+// SignalConnectFailure, SignalConnectTimeout, SignalSocketClosed and
+// SignalReadPacket.
+class RelayPortTest : public testing::Test,
+                      public sigslot::has_slots<> {
+ public:
+  RelayPortTest()
+      : main_(talk_base::Thread::Current()),
+        physical_socket_server_(new talk_base::PhysicalSocketServer),
+        virtual_socket_server_(new talk_base::VirtualSocketServer(
+            physical_socket_server_.get())),
+        ss_scope_(virtual_socket_server_.get()),
+        network_("unittest", "unittest", talk_base::IPAddress(INADDR_ANY)),
+        socket_factory_(talk_base::Thread::Current()),
+        username_(talk_base::CreateRandomString(16)),
+        password_(talk_base::CreateRandomString(16)),
+        relay_port_(cricket::RelayPort::Create(main_, &socket_factory_,
+                                               &network_,
+                                               kLocalAddress.ipaddr(),
+                                               0, 0, username_, password_, "")),
+        relay_server_(new cricket::RelayServer(main_)) {
+  }
+
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr) {
+    received_packet_count_[socket]++;
+  }
+
+  void OnConnectFailure(const cricket::ProtocolAddress* addr) {
+    failed_connections_.push_back(*addr);
+  }
+
+  void OnSoftTimeout(const cricket::ProtocolAddress* addr) {
+    soft_timedout_connections_.push_back(*addr);
+  }
+
+ protected:
+  static void SetUpTestCase() {
+    // Ensure the RNG is inited.
+    talk_base::InitRandom(NULL, 0);
+  }
+
+  virtual void SetUp() {
+    // The relay server needs an external socket to work properly.
+    talk_base::AsyncUDPSocket* ext_socket =
+        CreateAsyncUdpSocket(kRelayExtAddr);
+    relay_server_->AddExternalSocket(ext_socket);
+
+    // Listen for failures.
+    relay_port_->SignalConnectFailure.
+        connect(this, &RelayPortTest::OnConnectFailure);
+
+    // Listen for soft timeouts.
+    relay_port_->SignalSoftTimeout.
+        connect(this, &RelayPortTest::OnSoftTimeout);
+  }
+
+  // Udp has the highest 'goodness' value of the three different
+  // protocols used for connecting to the relay server. As soon as
+  // PrepareAddress is called, the RelayPort will start trying to
+  // connect to the given UDP address. As soon as a response to the
+  // sent STUN allocate request message has been received, the
+  // RelayPort will consider the connection to be complete and will
+  // abort any other connection attempts.
+  void TestConnectUdp() {
+    // Add a UDP socket to the relay server.
+    talk_base::AsyncUDPSocket* internal_udp_socket =
+        CreateAsyncUdpSocket(kRelayUdpAddr);
+    talk_base::AsyncSocket* server_socket = CreateServerSocket(kRelayTcpAddr);
+
+    relay_server_->AddInternalSocket(internal_udp_socket);
+    relay_server_->AddInternalServerSocket(server_socket, cricket::PROTO_TCP);
+
+    // Now add our relay addresses to the relay port and let it start.
+    relay_port_->AddServerAddress(
+        cricket::ProtocolAddress(kRelayUdpAddr, cricket::PROTO_UDP));
+    relay_port_->AddServerAddress(
+        cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP));
+    relay_port_->PrepareAddress();
+
+    // Should be connected.
+    EXPECT_TRUE_WAIT(relay_port_->IsReady(), kTimeoutMs);
+
+    // Make sure that we are happy with UDP, ie. not continuing with
+    // TCP, SSLTCP, etc.
+    WAIT(relay_server_->HasConnection(kRelayTcpAddr), kTimeoutMs);
+
+    // Should have only one connection.
+    EXPECT_EQ(1, relay_server_->GetConnectionCount());
+
+    // Should be the UDP address.
+    EXPECT_TRUE(relay_server_->HasConnection(kRelayUdpAddr));
+  }
+
+  // TCP has the second best 'goodness' value, and as soon as UDP
+  // connection has failed, the RelayPort will attempt to connect via
+  // TCP. Here we add a fake UDP address together with a real TCP
+  // address to simulate an UDP failure. As soon as UDP has failed the
+  // RelayPort will try the TCP adress and succed.
+  void TestConnectTcp() {
+    // Create a fake UDP address for relay port to simulate a failure.
+    cricket::ProtocolAddress fake_protocol_address =
+        cricket::ProtocolAddress(kRelayUdpAddr, cricket::PROTO_UDP);
+
+    // Create a server socket for the RelayServer.
+    talk_base::AsyncSocket* server_socket = CreateServerSocket(kRelayTcpAddr);
+    relay_server_->AddInternalServerSocket(server_socket, cricket::PROTO_TCP);
+
+    // Add server addresses to the relay port and let it start.
+    relay_port_->AddServerAddress(
+        cricket::ProtocolAddress(fake_protocol_address));
+    relay_port_->AddServerAddress(
+        cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP));
+    relay_port_->PrepareAddress();
+
+    EXPECT_FALSE(relay_port_->IsReady());
+
+    // Should have timed out in 200 + 200 + 400 + 800 + 1600 ms.
+    EXPECT_TRUE_WAIT(HasFailed(&fake_protocol_address), 3600);
+
+    // Wait until relayport is ready.
+    EXPECT_TRUE_WAIT(relay_port_->IsReady(), kMaxTimeoutMs);
+
+    // Should have only one connection.
+    EXPECT_EQ(1, relay_server_->GetConnectionCount());
+
+    // Should be the TCP address.
+    EXPECT_TRUE(relay_server_->HasConnection(kRelayTcpAddr));
+  }
+
+  void TestConnectSslTcp() {
+    // Create a fake TCP address for relay port to simulate a failure.
+    // We skip UDP here since transition from UDP to TCP has been
+    // tested above.
+    cricket::ProtocolAddress fake_protocol_address =
+        cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP);
+
+    // Create a ssl server socket for the RelayServer.
+    talk_base::AsyncSocket* ssl_server_socket =
+        CreateServerSocket(kRelaySslAddr);
+    relay_server_->AddInternalServerSocket(ssl_server_socket,
+                                           cricket::PROTO_SSLTCP);
+
+    // Create a tcp server socket that listens on the fake address so
+    // the relay port can attempt to connect to it.
+    talk_base::scoped_ptr<talk_base::AsyncSocket> tcp_server_socket(
+        CreateServerSocket(kRelayTcpAddr));
+
+    // Add server addresses to the relay port and let it start.
+    relay_port_->AddServerAddress(fake_protocol_address);
+    relay_port_->AddServerAddress(
+        cricket::ProtocolAddress(kRelaySslAddr, cricket::PROTO_SSLTCP));
+    relay_port_->PrepareAddress();
+    EXPECT_FALSE(relay_port_->IsReady());
+
+    // Should have timed out in 3000 ms(relayport.cc, kSoftConnectTimeoutMs).
+    EXPECT_TRUE_WAIT_MARGIN(HasTimedOut(&fake_protocol_address), 3000, 100);
+
+    // Wait until relayport is ready.
+    EXPECT_TRUE_WAIT(relay_port_->IsReady(), kMaxTimeoutMs);
+
+    // Should have only one connection.
+    EXPECT_EQ(1, relay_server_->GetConnectionCount());
+
+    // Should be the SSLTCP address.
+    EXPECT_TRUE(relay_server_->HasConnection(kRelaySslAddr));
+  }
+
+ private:
+  talk_base::AsyncUDPSocket* CreateAsyncUdpSocket(const SocketAddress addr) {
+    talk_base::AsyncSocket* socket =
+        virtual_socket_server_->CreateAsyncSocket(SOCK_DGRAM);
+    talk_base::AsyncUDPSocket* packet_socket =
+        talk_base::AsyncUDPSocket::Create(socket, addr);
+    EXPECT_TRUE(packet_socket != NULL);
+    packet_socket->SignalReadPacket.connect(this, &RelayPortTest::OnReadPacket);
+    return packet_socket;
+  }
+
+  talk_base::AsyncSocket* CreateServerSocket(const SocketAddress addr) {
+    talk_base::AsyncSocket* socket =
+        virtual_socket_server_->CreateAsyncSocket(SOCK_STREAM);
+    EXPECT_GE(socket->Bind(addr), 0);
+    EXPECT_GE(socket->Listen(5), 0);
+    return socket;
+  }
+
+  bool HasFailed(cricket::ProtocolAddress* addr) {
+    for (size_t i = 0; i < failed_connections_.size(); i++) {
+      if (failed_connections_[i].address == addr->address &&
+          failed_connections_[i].proto == addr->proto) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool HasTimedOut(cricket::ProtocolAddress* addr) {
+    for (size_t i = 0; i < soft_timedout_connections_.size(); i++) {
+      if (soft_timedout_connections_[i].address == addr->address &&
+          soft_timedout_connections_[i].proto == addr->proto) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  typedef std::map<talk_base::AsyncPacketSocket*, int> PacketMap;
+
+  talk_base::Thread* main_;
+  talk_base::scoped_ptr<talk_base::PhysicalSocketServer>
+      physical_socket_server_;
+  talk_base::scoped_ptr<talk_base::VirtualSocketServer> virtual_socket_server_;
+  talk_base::SocketServerScope ss_scope_;
+  talk_base::Network network_;
+  talk_base::BasicPacketSocketFactory socket_factory_;
+  std::string username_;
+  std::string password_;
+  talk_base::scoped_ptr<cricket::RelayPort> relay_port_;
+  talk_base::scoped_ptr<cricket::RelayServer> relay_server_;
+  std::vector<cricket::ProtocolAddress> failed_connections_;
+  std::vector<cricket::ProtocolAddress> soft_timedout_connections_;
+  PacketMap received_packet_count_;
+};
+
+TEST_F(RelayPortTest, ConnectUdp) {
+  TestConnectUdp();
+}
+
+TEST_F(RelayPortTest, ConnectTcp) {
+  TestConnectTcp();
+}
+
+TEST_F(RelayPortTest, ConnectSslTcp) {
+  TestConnectSslTcp();
+}
diff --git a/talk/p2p/base/relayserver.cc b/talk/p2p/base/relayserver.cc
index 554337f..b17da5b 100644
--- a/talk/p2p/base/relayserver.cc
+++ b/talk/p2p/base/relayserver.cc
@@ -444,7 +444,7 @@
 
   StunAddressAttribute* addr_attr =
       StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
-  addr_attr->SetIP(ext_addr.ip());
+  addr_attr->SetIP(ext_addr.ipaddr());
   addr_attr->SetPort(ext_addr.port());
   response.AddAttribute(addr_attr);
 
@@ -478,7 +478,7 @@
     return;
   }
 
-  talk_base::SocketAddress ext_addr(addr_attr->ip(), addr_attr->port());
+  talk_base::SocketAddress ext_addr(addr_attr->ipaddr(), addr_attr->port());
   RelayServerConnection* ext_conn =
       int_conn->binding()->GetExternalConnection(ext_addr);
   if (!ext_conn) {
@@ -620,7 +620,7 @@
 
   StunAddressAttribute* addr_attr =
       StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2);
-  addr_attr->SetIP(from_addr.ip());
+  addr_attr->SetIP(from_addr.ipaddr());
   addr_attr->SetPort(from_addr.port());
   msg.AddAttribute(addr_attr);
 
diff --git a/talk/p2p/base/relayserver.h b/talk/p2p/base/relayserver.h
index d34c099..a5fcc24 100644
--- a/talk/p2p/base/relayserver.h
+++ b/talk/p2p/base/relayserver.h
@@ -35,7 +35,7 @@
 #include "talk/base/asyncudpsocket.h"
 #include "talk/base/socketaddresspair.h"
 #include "talk/base/thread.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 #include "talk/p2p/base/port.h"
 #include "talk/p2p/base/stun.h"
 
diff --git a/talk/p2p/base/relayserver_unittest.cc b/talk/p2p/base/relayserver_unittest.cc
new file mode 100644
index 0000000..0f96e27
--- /dev/null
+++ b/talk/p2p/base/relayserver_unittest.cc
@@ -0,0 +1,546 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/testclient.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/relayserver.h"
+
+using talk_base::SocketAddress;
+using namespace cricket;
+
+static const uint32 LIFETIME = 4;  // seconds
+static const SocketAddress server_int_addr("127.0.0.1", 5000);
+static const SocketAddress server_ext_addr("127.0.0.1", 5001);
+static const SocketAddress client1_addr("127.0.0.1", 6000 + (rand() % 1000));
+static const SocketAddress client2_addr("127.0.0.1", 7000 + (rand() % 1000));
+static const char* bad = "this is a completely nonsensical message whose only "
+                         "purpose is to make the parser go 'ack'.  it doesn't "
+                         "look anything like a normal stun message";
+static const char* msg1 = "spamspamspamspamspamspamspambakedbeansspam";
+static const char* msg2 = "Lobster Thermidor a Crevette with a mornay sauce...";
+
+class RelayServerTest : public testing::Test {
+ public:
+  static void SetUpTestCase() {
+    talk_base::InitRandom(NULL, 0);
+  }
+  RelayServerTest()
+      : main_(talk_base::Thread::Current()), ss_(main_->socketserver()),
+        username_(talk_base::CreateRandomString(12)),
+        password_(talk_base::CreateRandomString(12)) {
+  }
+ protected:
+  virtual void SetUp() {
+    server_.reset(new RelayServer(main_));
+
+    server_->AddInternalSocket(
+        talk_base::AsyncUDPSocket::Create(ss_, server_int_addr));
+    server_->AddExternalSocket(
+        talk_base::AsyncUDPSocket::Create(ss_, server_ext_addr));
+
+    client1_.reset(new talk_base::TestClient(
+        talk_base::AsyncUDPSocket::Create(ss_, client1_addr)));
+    client2_.reset(new talk_base::TestClient(
+        talk_base::AsyncUDPSocket::Create(ss_, client2_addr)));
+  }
+
+  void Allocate() {
+    talk_base::scoped_ptr<StunMessage> req(
+        CreateStunMessage(STUN_ALLOCATE_REQUEST));
+    AddUsernameAttr(req.get(), username_);
+    AddLifetimeAttr(req.get(), LIFETIME);
+    Send1(req.get());
+    delete Receive1();
+  }
+  void Bind() {
+    talk_base::scoped_ptr<StunMessage> req(
+        CreateStunMessage(STUN_BINDING_REQUEST));
+    AddUsernameAttr(req.get(), username_);
+    Send2(req.get());
+    delete Receive1();
+  }
+
+  void Send1(const StunMessage* msg) {
+    talk_base::ByteBuffer buf;
+    msg->Write(&buf);
+    SendRaw1(buf.Data(), buf.Length());
+  }
+  void Send2(const StunMessage* msg) {
+    talk_base::ByteBuffer buf;
+    msg->Write(&buf);
+    SendRaw2(buf.Data(), buf.Length());
+  }
+  void SendRaw1(const char* data, int len) {
+    return Send(client1_.get(), data, len, server_int_addr);
+  }
+  void SendRaw2(const char* data, int len) {
+    return Send(client2_.get(), data, len, server_ext_addr);
+  }
+  void Send(talk_base::TestClient* client, const char* data,
+            int len, const SocketAddress& addr) {
+    client->SendTo(data, len, addr);
+  }
+
+  StunMessage* Receive1() {
+    return Receive(client1_.get());
+  }
+  StunMessage* Receive2() {
+    return Receive(client2_.get());
+  }
+  std::string ReceiveRaw1() {
+    return ReceiveRaw(client1_.get());
+  }
+  std::string ReceiveRaw2() {
+    return ReceiveRaw(client2_.get());
+  }
+  StunMessage* Receive(talk_base::TestClient* client) {
+    StunMessage* msg = NULL;
+    talk_base::TestClient::Packet* packet = client->NextPacket();
+    if (packet) {
+      talk_base::ByteBuffer buf(packet->buf, packet->size);
+      msg = new StunMessage();
+      msg->Read(&buf);
+      delete packet;
+    }
+    return msg;
+  }
+  std::string ReceiveRaw(talk_base::TestClient* client) {
+    std::string raw;
+    talk_base::TestClient::Packet* packet = client->NextPacket();
+    if (packet) {
+      raw = std::string(packet->buf, packet->size);
+      delete packet;
+    }
+    return raw;
+  }
+
+  static StunMessage* CreateStunMessage(StunMessageType type) {
+    StunMessage* msg = new StunMessage();
+    msg->SetType(type);
+    msg->SetTransactionID(
+        talk_base::CreateRandomString(kStunTransactionIdLength));
+    return msg;
+  }
+  static void AddMagicCookieAttr(StunMessage* msg) {
+    StunByteStringAttribute* attr =
+        StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+    attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE, sizeof(TURN_MAGIC_COOKIE_VALUE));
+    msg->AddAttribute(attr);
+  }
+  static void AddUsernameAttr(StunMessage* msg, const std::string& val) {
+    StunByteStringAttribute* attr =
+        StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+    attr->CopyBytes(val.c_str(), val.size());
+    msg->AddAttribute(attr);
+  }
+  static void AddLifetimeAttr(StunMessage* msg, int val) {
+    StunUInt32Attribute* attr =
+        StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
+    attr->SetValue(val);
+    msg->AddAttribute(attr);
+  }
+  static void AddDestinationAttr(StunMessage* msg, const SocketAddress& addr) {
+    StunAddressAttribute* attr =
+        StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+    attr->SetIP(addr.ipaddr());
+    attr->SetPort(addr.port());
+    msg->AddAttribute(attr);
+  }
+
+  talk_base::Thread* main_;
+  talk_base::SocketServer* ss_;
+  talk_base::scoped_ptr<RelayServer> server_;
+  talk_base::scoped_ptr<talk_base::TestClient> client1_;
+  talk_base::scoped_ptr<talk_base::TestClient> client2_;
+  std::string username_;
+  std::string password_;
+};
+
+// Send a complete nonsense message and verify that it is rejected.
+TEST_F(RelayServerTest, TestBadRequest) {
+  talk_base::scoped_ptr<StunMessage> res;
+
+  SendRaw1(bad, std::strlen(bad));
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type());
+
+  const StunErrorCodeAttribute* err = res->GetErrorCode();
+  ASSERT_TRUE(err != NULL);
+  EXPECT_EQ(4, err->error_class());
+  EXPECT_EQ(0, err->number());
+  EXPECT_EQ("Bad Request", err->reason());
+}
+
+// Send an allocate request without a username and verify it is rejected.
+TEST_F(RelayServerTest, TestAllocateNoUsername) {
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_ALLOCATE_REQUEST)), res;
+
+  Send1(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_ALLOCATE_ERROR_RESPONSE, res->type());
+  EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+  const StunErrorCodeAttribute* err = res->GetErrorCode();
+  ASSERT_TRUE(err != NULL);
+  EXPECT_EQ(4, err->error_class());
+  EXPECT_EQ(32, err->number());
+  EXPECT_EQ("Missing Username", err->reason());
+}
+
+// Send a binding request and verify that it is rejected.
+TEST_F(RelayServerTest, TestBindingRequest) {
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_BINDING_REQUEST)), res;
+  AddUsernameAttr(req.get(), username_);
+
+  Send1(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type());
+  EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+  const StunErrorCodeAttribute* err = res->GetErrorCode();
+  ASSERT_TRUE(err != NULL);
+  EXPECT_EQ(6, err->error_class());
+  EXPECT_EQ(0, err->number());
+  EXPECT_EQ("Operation Not Supported", err->reason());
+}
+
+// Send an allocate request and verify that it is accepted.
+TEST_F(RelayServerTest, TestAllocate) {
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_ALLOCATE_REQUEST)), res;
+  AddUsernameAttr(req.get(), username_);
+  AddLifetimeAttr(req.get(), LIFETIME);
+
+  Send1(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_ALLOCATE_RESPONSE, res->type());
+  EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+  const StunAddressAttribute* mapped_addr =
+      res->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+  ASSERT_TRUE(mapped_addr != NULL);
+  EXPECT_EQ(1, mapped_addr->family());
+  EXPECT_EQ(server_ext_addr.port(), mapped_addr->port());
+  EXPECT_EQ(server_ext_addr.ipaddr(), mapped_addr->ipaddr());
+
+  const StunUInt32Attribute* res_lifetime_attr =
+      res->GetUInt32(STUN_ATTR_LIFETIME);
+  ASSERT_TRUE(res_lifetime_attr != NULL);
+  EXPECT_EQ(LIFETIME, res_lifetime_attr->value());
+}
+
+// Send a second allocate request and verify that it is also accepted, though
+// the lifetime should be ignored.
+TEST_F(RelayServerTest, TestReallocate) {
+  Allocate();
+
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_ALLOCATE_REQUEST)), res;
+  AddMagicCookieAttr(req.get());
+  AddUsernameAttr(req.get(), username_);
+
+  Send1(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_ALLOCATE_RESPONSE, res->type());
+  EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+  const StunAddressAttribute* mapped_addr =
+      res->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+  ASSERT_TRUE(mapped_addr != NULL);
+  EXPECT_EQ(1, mapped_addr->family());
+  EXPECT_EQ(server_ext_addr.port(), mapped_addr->port());
+  EXPECT_EQ(server_ext_addr.ipaddr(), mapped_addr->ipaddr());
+
+  const StunUInt32Attribute* lifetime_attr =
+      res->GetUInt32(STUN_ATTR_LIFETIME);
+  ASSERT_TRUE(lifetime_attr != NULL);
+  EXPECT_EQ(LIFETIME, lifetime_attr->value());
+}
+
+// Send a request from another client and see that it arrives at the first
+// client in the binding.
+TEST_F(RelayServerTest, TestRemoteBind) {
+  Allocate();
+
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_BINDING_REQUEST)), res;
+  AddUsernameAttr(req.get(), username_);
+
+  Send2(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_DATA_INDICATION, res->type());
+
+  const StunByteStringAttribute* recv_data =
+      res->GetByteString(STUN_ATTR_DATA);
+  ASSERT_TRUE(recv_data != NULL);
+
+  talk_base::ByteBuffer buf(recv_data->bytes(), recv_data->length());
+  talk_base::scoped_ptr<StunMessage> res2(new StunMessage());
+  EXPECT_TRUE(res2->Read(&buf));
+  EXPECT_EQ(STUN_BINDING_REQUEST, res2->type());
+  EXPECT_EQ(req->transaction_id(), res2->transaction_id());
+
+  const StunAddressAttribute* src_addr =
+      res->GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+  ASSERT_TRUE(src_addr != NULL);
+  EXPECT_EQ(1, src_addr->family());
+  EXPECT_EQ(client2_addr.ipaddr(), src_addr->ipaddr());
+  EXPECT_EQ(client2_addr.port(), src_addr->port());
+
+  EXPECT_TRUE(Receive2() == NULL);
+}
+
+// Send a complete nonsense message to the established connection and verify
+// that it is dropped by the server.
+TEST_F(RelayServerTest, TestRemoteBadRequest) {
+  Allocate();
+  Bind();
+
+  SendRaw1(bad, std::strlen(bad));
+  EXPECT_TRUE(Receive1() == NULL);
+  EXPECT_TRUE(Receive2() == NULL);
+}
+
+// Send a send request without a username and verify it is rejected.
+TEST_F(RelayServerTest, TestSendRequestMissingUsername) {
+  Allocate();
+  Bind();
+
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_SEND_REQUEST)), res;
+  AddMagicCookieAttr(req.get());
+
+  Send1(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+  EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+  const StunErrorCodeAttribute* err = res->GetErrorCode();
+  ASSERT_TRUE(err != NULL);
+  EXPECT_EQ(4, err->error_class());
+  EXPECT_EQ(32, err->number());
+  EXPECT_EQ("Missing Username", err->reason());
+}
+
+// Send a send request with the wrong username and verify it is rejected.
+TEST_F(RelayServerTest, TestSendRequestBadUsername) {
+  Allocate();
+  Bind();
+
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_SEND_REQUEST)), res;
+  AddMagicCookieAttr(req.get());
+  AddUsernameAttr(req.get(), "foobarbizbaz");
+
+  Send1(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+  EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+  const StunErrorCodeAttribute* err = res->GetErrorCode();
+  ASSERT_TRUE(err != NULL);
+  EXPECT_EQ(4, err->error_class());
+  EXPECT_EQ(30, err->number());
+  EXPECT_EQ("Stale Credentials", err->reason());
+}
+
+// Send a send request without a destination address and verify that it is
+// rejected.
+TEST_F(RelayServerTest, TestSendRequestNoDestinationAddress) {
+  Allocate();
+  Bind();
+
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_SEND_REQUEST)), res;
+  AddMagicCookieAttr(req.get());
+  AddUsernameAttr(req.get(), username_);
+
+  Send1(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+  EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+  const StunErrorCodeAttribute* err = res->GetErrorCode();
+  ASSERT_TRUE(err != NULL);
+  EXPECT_EQ(4, err->error_class());
+  EXPECT_EQ(0, err->number());
+  EXPECT_EQ("Bad Request", err->reason());
+}
+
+// Send a send request without data and verify that it is rejected.
+TEST_F(RelayServerTest, TestSendRequestNoData) {
+  Allocate();
+  Bind();
+
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_SEND_REQUEST)), res;
+  AddMagicCookieAttr(req.get());
+  AddUsernameAttr(req.get(), username_);
+  AddDestinationAttr(req.get(), client2_addr);
+
+  Send1(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+  EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+  const StunErrorCodeAttribute* err = res->GetErrorCode();
+  ASSERT_TRUE(err != NULL);
+  EXPECT_EQ(4, err->error_class());
+  EXPECT_EQ(00, err->number());
+  EXPECT_EQ("Bad Request", err->reason());
+}
+
+// Send a binding request after an allocate and verify that it is rejected.
+TEST_F(RelayServerTest, TestSendRequestWrongType) {
+  Allocate();
+  Bind();
+
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_BINDING_REQUEST)), res;
+  AddMagicCookieAttr(req.get());
+  AddUsernameAttr(req.get(), username_);
+
+  Send1(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type());
+  EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+  const StunErrorCodeAttribute* err = res->GetErrorCode();
+  ASSERT_TRUE(err != NULL);
+  EXPECT_EQ(6, err->error_class());
+  EXPECT_EQ(0, err->number());
+  EXPECT_EQ("Operation Not Supported", err->reason());
+}
+
+// Verify that we can send traffic back and forth between the clients after a
+// successful allocate and bind.
+TEST_F(RelayServerTest, TestSendRaw) {
+  Allocate();
+  Bind();
+
+  for (int i = 0; i < 10; i++) {
+    talk_base::scoped_ptr<StunMessage> req(
+        CreateStunMessage(STUN_SEND_REQUEST)), res;
+    AddMagicCookieAttr(req.get());
+    AddUsernameAttr(req.get(), username_);
+    AddDestinationAttr(req.get(), client2_addr);
+
+    StunByteStringAttribute* send_data =
+        StunAttribute::CreateByteString(STUN_ATTR_DATA);
+    send_data->CopyBytes(msg1);
+    req->AddAttribute(send_data);
+
+    Send1(req.get());
+    EXPECT_EQ(msg1, ReceiveRaw2());
+    SendRaw2(msg2, std::strlen(msg2));
+    res.reset(Receive1());
+
+    ASSERT_TRUE(res.get() != NULL);
+    EXPECT_EQ(STUN_DATA_INDICATION, res->type());
+
+    const StunAddressAttribute* src_addr =
+        res->GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+    ASSERT_TRUE(src_addr != NULL);
+    EXPECT_EQ(1, src_addr->family());
+    EXPECT_EQ(client2_addr.ipaddr(), src_addr->ipaddr());
+    EXPECT_EQ(client2_addr.port(), src_addr->port());
+
+    const StunByteStringAttribute* recv_data =
+        res->GetByteString(STUN_ATTR_DATA);
+    ASSERT_TRUE(recv_data != NULL);
+    EXPECT_EQ(strlen(msg2), recv_data->length());
+    EXPECT_EQ(0, memcmp(msg2, recv_data->bytes(), recv_data->length()));
+  }
+}
+
+// Verify that a binding expires properly, and rejects send requests.
+TEST_F(RelayServerTest, TestExpiration) {
+  Allocate();
+  Bind();
+
+  // Wait twice the lifetime to make sure the server has expired the binding.
+  talk_base::Thread::Current()->ProcessMessages((LIFETIME * 2) * 1000);
+
+  talk_base::scoped_ptr<StunMessage> req(
+      CreateStunMessage(STUN_SEND_REQUEST)), res;
+  AddMagicCookieAttr(req.get());
+  AddUsernameAttr(req.get(), username_);
+  AddDestinationAttr(req.get(), client2_addr);
+
+  StunByteStringAttribute* data_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_DATA);
+  data_attr->CopyBytes(msg1);
+  req->AddAttribute(data_attr);
+
+  Send1(req.get());
+  res.reset(Receive1());
+
+  ASSERT_TRUE(res.get() != NULL);
+  EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+
+  const StunErrorCodeAttribute* err = res->GetErrorCode();
+  ASSERT_TRUE(err != NULL);
+  EXPECT_EQ(6, err->error_class());
+  EXPECT_EQ(0, err->number());
+  EXPECT_EQ("Operation Not Supported", err->reason());
+
+  // Also verify that traffic from the external client is ignored.
+  SendRaw2(msg2, std::strlen(msg2));
+  EXPECT_TRUE(ReceiveRaw1().empty());
+}
diff --git a/talk/p2p/base/session.cc b/talk/p2p/base/session.cc
index b5be361..aba5ac4 100644
--- a/talk/p2p/base/session.cc
+++ b/talk/p2p/base/session.cc
@@ -32,11 +32,11 @@
 #include "talk/base/scoped_ptr.h"
 #include "talk/xmpp/constants.h"
 #include "talk/xmpp/jid.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
 #include "talk/p2p/base/sessionclient.h"
 #include "talk/p2p/base/transport.h"
 #include "talk/p2p/base/transportchannelproxy.h"
-#include "talk/p2p/base/p2ptransport.h"
-#include "talk/p2p/base/p2ptransportchannel.h"
 
 #include "talk/p2p/base/constants.h"
 
@@ -64,13 +64,21 @@
     iter->second->SignalDestroyed(iter->second);
     delete iter->second;
   }
-  delete transport_;
+  if (owner_)
+    delete transport_;
 }
 
 std::string TransportProxy::type() const {
   return transport_->type();
 }
 
+void TransportProxy::SetImplementation(Transport* impl, bool owner) {
+  if (owner_ && transport_)
+     delete transport_;
+  transport_ = impl;
+  owner_ = owner;
+}
+
 TransportChannel* TransportProxy::GetChannel(const std::string& name) {
   return GetProxy(name);
 }
@@ -148,6 +156,7 @@
   TransportChannelImpl* impl = transport_->GetChannel(name);
   if (impl == NULL) {
     impl = transport_->CreateChannel(name, content_type);
+    impl->set_session_id(sid_);
   }
   return impl;
 }
@@ -156,7 +165,34 @@
     const std::string& name, TransportChannelProxy* proxy) {
   TransportChannelImpl* impl = GetOrCreateImpl(name, proxy->content_type());
   ASSERT(impl != NULL);
-  proxy->SetImplementation(impl);
+  proxy->SetImplementation(impl, true);
+}
+
+// This method will use TransportChannelImpls of and deletes what it owns.
+void TransportProxy::CopyTransportProxyChannels(TransportProxy* proxy) {
+  size_t index = 0;
+  for (ChannelMap::const_iterator iter = proxy->channels().begin();
+       iter != proxy->channels().end(); ++iter, ++index) {
+    ReplaceImpl(iter->second, index);
+  }
+}
+
+void TransportProxy::ReplaceImpl(TransportChannelProxy* channel,
+                                 size_t index) {
+  if (index < channels().size()) {
+    ChannelMap::const_iterator iter = channels().begin();
+    // Get handle the index which needs to be replaced.
+    for (size_t i = 0; i < index; ++i, ++iter);
+
+    TransportChannelProxy* target_channel = iter->second;
+    if (target_channel) {
+      // Deleting TransportChannelImpl before replacing it.
+      transport_->DestroyChannel(iter->first);
+      target_channel->SetImplementation(channel->impl(), false);
+    }
+  } else {
+    LOG(LS_WARNING) << "invalid TransportChannelProxy index to replace";
+  }
 }
 
 BaseSession::BaseSession(talk_base::Thread* signaling_thread,
@@ -249,7 +285,7 @@
   transport->SignalChannelGone.connect(
       this, &BaseSession::OnTransportChannelGone);
 
-  transproxy = new TransportProxy(content_name, transport);
+  transproxy = new TransportProxy(sid_, content_name, transport);
   transports_[content_name] = transproxy;
 
   return transproxy;
@@ -323,6 +359,59 @@
   }
 }
 
+bool BaseSession::ContentsGrouped() {
+  // TODO - present implementation checks for groups present
+  // in SDP. It may be necessary to check content_names in groups of both
+  // local and remote descriptions. Assumption here is that when this method
+  // returns true, media contents can be muxed.
+  if (local_description()->HasGroup(GN_BUNDLE) &&
+      remote_description()->HasGroup(GN_BUNDLE)) {
+    return true;
+  }
+  return false;
+}
+
+bool BaseSession::MaybeEnableMuxingSupport() {
+  bool ret = true;
+  if (!ContentsGrouped()) {
+    LOG(LS_INFO) << "Contents are not grouped together cannot be muxed";
+  } else {
+    // Always use first content name from the group for muxing. Hence ordering
+    // of content names in SDP should match to the order in group.
+    const ContentGroup* muxed_content_group =
+        local_description()->GetGroupByName(GN_BUNDLE);
+    const std::string* content_name =
+        muxed_content_group->FirstContentName();
+    if (content_name) {
+      const ContentInfo* content =
+          local_description_->GetContentByName(*content_name);
+      ASSERT(content != NULL);
+      SetSelectedProxy(content->name, muxed_content_group);
+    }
+  }
+  return ret;
+}
+
+void BaseSession::SetSelectedProxy(const std::string& content_name,
+                                   const ContentGroup* muxed_group) {
+  TransportProxy* selected_proxy = GetTransportProxy(content_name);
+  if (selected_proxy) {
+    ASSERT(selected_proxy->negotiated());
+    for (TransportMap::iterator iter = transports_.begin();
+         iter != transports_.end(); ++iter) {
+      // If content is part of group, then try to replace the Proxy with
+      // the selected.
+      if (iter->first != content_name &&
+          muxed_group->HasContentName(iter->first)) {
+        TransportProxy* proxy = iter->second;
+        proxy->CopyTransportProxyChannels(selected_proxy);
+        // After replacing the TransportChannels, replace Transport
+        proxy->SetImplementation(selected_proxy->impl(), false);
+      }
+    }
+  }
+}
+
 void BaseSession::OnMessage(talk_base::Message *pmsg) {
   switch (pmsg->message_id) {
   case MSG_TIMEOUT:
@@ -414,6 +503,7 @@
     return false;
   }
 
+  MaybeEnableMuxingSupport();  // Enable transport channel mux if supported.
   SetState(Session::STATE_SENTACCEPT);
   return true;
 }
@@ -648,8 +738,8 @@
     case ACTION_TRANSPORT_ACCEPT:
       valid = OnTransportAcceptMessage(msg, &error);
       break;
-    case ACTION_UPDATE:
-      valid = OnUpdateMessage(msg, &error);
+    case ACTION_DESCRIPTION_INFO:
+      valid = OnDescriptionInfoMessage(msg, &error);
       break;
     default:
       valid = BadMessage(buzz::QN_STANZA_BAD_REQUEST,
@@ -796,6 +886,7 @@
   OnInitiateAcked();
 
   set_remote_description(new SessionDescription(accept.ClearContents()));
+  MaybeEnableMuxingSupport();  // Enable transport channel mux if supported.
   SetState(STATE_RECEIVEDACCEPT);
 
   // Users of Session may listen to state change and call Reject().
@@ -856,7 +947,7 @@
   return true;
 }
 
-bool Session::OnUpdateMessage(const SessionMessage& msg,
+bool Session::OnDescriptionInfoMessage(const SessionMessage& msg,
                               MessageError* error) {
   if (!CheckState(STATE_INPROGRESS, error))
     return false;
@@ -869,27 +960,33 @@
   }
 
   ContentInfos updated_contents = description_info.ClearContents();
-  ContentInfos::iterator it;
 
+  // TODO: Currently, reflector sends back
+  // video stream updates even for an audio-only call, which causes
+  // this to fail.  Put this back once reflector is fixed.
+  //
+  // ContentInfos::iterator it;
   // First, ensure all updates are valid before modifying remote_description_.
-  for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
-    if (remote_description()->GetContentByName(it->name) == NULL) {
-      return false;
-    }
+  // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
+  //   if (remote_description()->GetContentByName(it->name) == NULL) {
+  //     return false;
+  //   }
+  // }
 
-    // TODO: We should add a check to ensure that the updated
-    // contents are compatible with the original contents.
-  }
+  // TODO: We used to replace contents from an update, but
+  // that no longer works with partial updates.  We need to figure out
+  // a way to merge patial updates into contents.  For now, users of
+  // Session should listen to SignalRemoteDescriptionUpdate and handle
+  // updates.  They should not expect remote_description to be the
+  // latest value.
+  //
+  // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
+  //     remote_description()->RemoveContentByName(it->name);
+  //     remote_description()->AddContent(it->name, it->type, it->description);
+  //   }
+  // }
 
-  // Merge the updates into the remote description.
-  for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
-    LOG(LS_INFO) << "Updating content " << it->name;
-    remote_description()->RemoveContentByName(it->name);
-    remote_description()->AddContent(it->name, it->type, it->description);
-  }
-
-  SignalRemoteDescriptionUpdate(this);
-
+  SignalRemoteDescriptionUpdate(this, updated_contents);
   return true;
 }
 
diff --git a/talk/p2p/base/session.h b/talk/p2p/base/session.h
index cb3c1fc..b57468b 100644
--- a/talk/p2p/base/session.h
+++ b/talk/p2p/base/session.h
@@ -76,17 +76,29 @@
 // initiate, and for speculatively connecting channels.  Previously, a
 // session had one ChannelMap and transport.  Now, with multiple
 // transports per session, we need multiple ChannelMaps as well.
+
+typedef std::map<std::string, TransportChannelProxy*> ChannelMap;
+
 class TransportProxy {
  public:
-  TransportProxy(const std::string& content_name, Transport* transport)
-      : content_name_(content_name),
+  TransportProxy(
+      const std::string& sid,
+      const std::string& content_name,
+      Transport* transport)
+      : sid_(sid),
+        content_name_(content_name),
         transport_(transport),
+        owner_(true),
         state_(STATE_INIT),
         sent_candidates_(false) {}
   ~TransportProxy();
 
   std::string content_name() const { return content_name_; }
   Transport* impl() const { return transport_; }
+  // TransportProxy can contain a pointer to Transport for which it's not the
+  // actual owner. In that case it shouldn't try to delete Transport object.
+  // TODO - Remove this hack when ref count support is available.
+  void SetImplementation(Transport* impl, bool owner);
   std::string type() const;
   bool negotiated() const { return state_ == STATE_NEGOTIATED; }
   const Candidates& sent_candidates() const { return sent_candidates_; }
@@ -102,6 +114,8 @@
   void ClearUnsentCandidates() { unsent_candidates_.clear(); }
   void SpeculativelyConnectChannels();
   void CompleteNegotiation();
+  void CopyTransportProxyChannels(TransportProxy* proxy);
+  const ChannelMap& channels() { return channels_; }
 
  private:
   enum TransportState {
@@ -110,15 +124,16 @@
     STATE_NEGOTIATED
   };
 
-  typedef std::map<std::string, TransportChannelProxy*> ChannelMap;
-
   TransportChannelProxy* GetProxy(const std::string& name);
+  void ReplaceImpl(TransportChannelProxy* channel_proxy, size_t index);
   TransportChannelImpl* GetOrCreateImpl(const std::string& name,
                                         const std::string& content_type);
   void SetProxyImpl(const std::string& name, TransportChannelProxy* proxy);
 
+  std::string sid_;
   std::string content_name_;
   Transport* transport_;
+  bool owner_;
   TransportState state_;
   ChannelMap channels_;
   Candidates sent_candidates_;
@@ -231,12 +246,12 @@
   // Returns the current state of the session.  See the enum above for details.
   // Each time the state changes, we will fire this signal.
   State state() const { return state_; }
-  sigslot::signal2<BaseSession *, State> SignalState;
+  sigslot::signal2<BaseSession* , State> SignalState;
 
   // Returns the last error in the session.  See the enum above for details.
   // Each time the an error occurs, we will fire this signal.
   Error error() const { return error_; }
-  sigslot::signal2<BaseSession *, Error> SignalError;
+  sigslot::signal2<BaseSession* , Error> SignalError;
 
   // Updates the state, signaling if necessary.
   virtual void SetState(State state);
@@ -244,8 +259,10 @@
   // Updates the error state, signaling if necessary.
   virtual void SetError(Error error);
 
-  // Fired when the remote description is updated.
-  sigslot::signal1<BaseSession *> SignalRemoteDescriptionUpdate;
+  // Fired when the remote description is updated, with the updated
+  // contents.
+  sigslot::signal2<BaseSession* , const ContentInfos&>
+      SignalRemoteDescriptionUpdate;
 
   // Returns the transport that has been negotiated or NULL if
   // negotiation is still in progress.
@@ -270,6 +287,7 @@
   // Send when doing so.
   virtual void DestroyChannel(const std::string& content_name,
                               const std::string& channel_name);
+
  protected:
   const TransportMap& transport_proxies() const { return transports_; }
   // Get a TransportProxy by content_name or transport. NULL if not found.
@@ -283,6 +301,9 @@
 
   void OnSignalingReady();
   void SpeculativelyConnectAllTransportChannels();
+  // This method will mux transport channels by content_name.
+  // First content is used for muxing.
+  bool MaybeEnableMuxingSupport();
 
   // Called when a transport requests signaling.
   virtual void OnTransportRequestSignaling(Transport* transport) {
@@ -325,10 +346,19 @@
   virtual void OnMessage(talk_base::Message *pmsg);
 
  protected:
-// private:
   State state_;
   Error error_;
+
  private:
+  // This method will check GroupInfo in local and remote SessionDescriptions.
+  bool ContentsGrouped();
+  // This method will delete the Transport and TransportChannelImpl's and
+  // replace those with the selected Transport objects. Selection is done
+  // based on the content_name and in this case first MediaContent information
+  // is used for mux.
+  void SetSelectedProxy(const std::string& content_name,
+                        const ContentGroup* muxed_group);
+
   talk_base::Thread* signaling_thread_;
   talk_base::Thread* worker_thread_;
   PortAllocator* port_allocator_;
@@ -511,7 +541,7 @@
   // sending of each message.  When messages are received by the other client,
   // they should be handed to OnIncomingMessage.
   // (These are called only by SessionManager.)
-  sigslot::signal2<Session *, const buzz::XmlElement*> SignalOutgoingMessage;
+  sigslot::signal2<Session* , const buzz::XmlElement*> SignalOutgoingMessage;
   void OnIncomingMessage(const SessionMessage& msg);
 
   void OnIncomingResponse(const buzz::XmlElement* orig_stanza,
@@ -539,7 +569,7 @@
   bool OnTerminateMessage(const SessionMessage& msg, MessageError* error);
   bool OnTransportInfoMessage(const SessionMessage& msg, MessageError* error);
   bool OnTransportAcceptMessage(const SessionMessage& msg, MessageError* error);
-  bool OnUpdateMessage(const SessionMessage& msg, MessageError* error);
+  bool OnDescriptionInfoMessage(const SessionMessage& msg, MessageError* error);
   bool OnRedirectError(const SessionRedirect& redirect, SessionError* error);
 
   // Verifies that we are in the appropriate state to receive this message.
diff --git a/talk/p2p/base/session_unittest.cc b/talk/p2p/base/session_unittest.cc
new file mode 100644
index 0000000..aec4986
--- /dev/null
+++ b/talk/p2p/base/session_unittest.cc
@@ -0,0 +1,2231 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <cstring>
+#include <sstream>
+#include <deque>
+#include <map>
+
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/natserver.h"
+#include "talk/base/natsocketfactory.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/relayserver.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/stunserver.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/p2p/base/transportchannelproxy.h"
+#include "talk/p2p/base/udpport.h"
+#include "talk/xmpp/constants.h"
+
+using cricket::SignalingProtocol;
+using cricket::PROTOCOL_HYBRID;
+using cricket::PROTOCOL_JINGLE;
+using cricket::PROTOCOL_GINGLE;
+
+static const std::string kInitiator = "init@init.com";
+static const std::string kResponder = "resp@resp.com";
+// Expected from test random number generator.
+static const std::string kSessionId = "2154761789";
+// TODO: When we need to test more than one transport type,
+// allow this to be injected like the content types are.
+static const std::string kTransportType = "http://www.google.com/transport/p2p";
+
+// Controls how long we wait for a session to send messages that we
+// expect, in milliseconds.  We put it high to avoid flaky tests.
+static const int kEventTimeout = 5000;
+
+static const int kNumPorts = 2;
+static const int kPort0 = 28653;
+static const int kPortStep = 5;
+
+static const std::string kNotifyNick1 = "derekcheng_google.com^59422C27";
+static const std::string kNotifyNick2 = "someoneelses_google.com^7abd6a7a20";
+static const uint32 kNotifyAudioSsrc1 = 2625839801U;
+static const uint32 kNotifyAudioSsrc2 = 2529430427U;
+static const uint32 kNotifyVideoSsrc1 = 3;
+static const uint32 kNotifyVideoSsrc2 = 2;
+
+static const std::string kViewRequestNick = "param_google.com^16A3CDBE";
+static const uint32 kViewRequestSsrc = 4;
+static const int kViewRequestWidth = 320;
+static const int kViewRequestHeight = 200;
+static const int kViewRequestFrameRate = 15;
+
+int GetPort(int port_index) {
+  return kPort0 + (port_index * kPortStep);
+}
+
+std::string GetPortString(int port_index) {
+  return talk_base::ToString(GetPort(port_index));
+}
+
+// Only works for port_index < 10, which is fine for our purposes.
+std::string GetUsername(int port_index) {
+  return "username" + std::string(8, talk_base::ToString(port_index)[0]);
+}
+
+// Only works for port_index < 10, which is fine for our purposes.
+std::string GetPassword(int port_index) {
+  return "password" + std::string(8, talk_base::ToString(port_index)[0]);
+}
+
+std::string IqAck(const std::string& id,
+                  const std::string& from,
+                  const std::string& to) {
+  return "<cli:iq"
+      " to=\"" + to + "\""
+      " id=\"" + id + "\""
+      " type=\"result\""
+      " from=\"" + from + "\""
+      " xmlns:cli=\"jabber:client\""
+      "/>";
+}
+
+std::string IqSet(const std::string& id,
+                  const std::string& from,
+                  const std::string& to,
+                  const std::string& content) {
+  return "<cli:iq"
+      " to=\"" + to + "\""
+      " type=\"set\""
+      " from=\"" + from + "\""
+      " id=\"" + id + "\""
+      " xmlns:cli=\"jabber:client\""
+      ">"
+      + content +
+      "</cli:iq>";
+}
+
+std::string IqError(const std::string& id,
+                    const std::string& from,
+                    const std::string& to,
+                    const std::string& content) {
+  return "<cli:error"
+      " to=\"" + to + "\""
+      " type=\"error\""
+      " from=\"" + from + "\""
+      " id=\"" + id + "\""
+      " xmlns:cli=\"jabber:client\""
+      ">"
+      + content +
+      "</cli:error>";
+}
+
+std::string GingleSessionXml(const std::string& type,
+                             const std::string& content) {
+  return "<session"
+      " xmlns=\"http://www.google.com/session\""
+      " type=\"" + type + "\""
+      " id=\"" + kSessionId + "\""
+      " initiator=\"" + kInitiator + "\""
+      ">"
+      + content +
+      "</session>";
+}
+
+std::string GingleDescriptionXml(const std::string& content_type) {
+  return "<description"
+      " xmlns=\"" + content_type + "\""
+      "/>";
+}
+
+std::string P2pCandidateXml(const std::string& name, int port_index) {
+  return "<candidate"
+      " name=\"" + name + "\""
+      " address=\"127.0.0.1\""
+      " port=\"" + GetPortString(port_index) + "\""
+      " preference=\"1\""
+      " username=\"" + GetUsername(port_index) + "\""
+      " protocol=\"udp\""
+      " generation=\"0\""
+      " password=\"" + GetPassword(port_index) + "\""
+      " type=\"local\""
+      " network=\"network\""
+      "/>";
+}
+
+std::string JingleActionXml(const std::string& action,
+                            const std::string& content) {
+  return "<jingle"
+      " xmlns=\"urn:xmpp:jingle:1\""
+      " action=\"" + action + "\""
+      " sid=\"" + kSessionId + "\""
+      ">"
+      + content +
+      "</jingle>";
+}
+
+std::string JingleInitiateActionXml(const std::string& content) {
+  return "<jingle"
+      " xmlns=\"urn:xmpp:jingle:1\""
+      " action=\"session-initiate\""
+      " sid=\"" + kSessionId + "\""
+      " initiator=\"" + kInitiator + "\""
+      ">"
+      + content +
+      "</jingle>";
+}
+
+std::string JingleEmptyContentXml(const std::string& content_name,
+                                  const std::string& content_type,
+                                  const std::string& transport_type) {
+  return "<content"
+      " name=\"" + content_name + "\""
+      " creator=\"initiator\""
+      ">"
+      "<description"
+      " xmlns=\"" + content_type + "\""
+      "/>"
+      "<transport"
+      " xmlns=\"" + transport_type + "\""
+      "/>"
+      "</content>";
+}
+
+std::string JingleContentXml(const std::string& content_name,
+                             const std::string& content_type,
+                             const std::string& transport_type,
+                             const std::string& transport_main) {
+  std::string transport = transport_type.empty() ? "" :
+      "<transport"
+      " xmlns=\"" + transport_type + "\""
+      ">"
+      + transport_main +
+      "</transport>";
+
+  return"<content"
+      " name=\"" + content_name + "\""
+      " creator=\"initiator\""
+      ">"
+      "<description"
+      " xmlns=\"" + content_type + "\""
+      "/>"
+      + transport +
+      "</content>";
+}
+
+std::string JingleTransportContentXml(const std::string& content_name,
+                                      const std::string& transport_type,
+                                      const std::string& content) {
+  return "<content"
+      " name=\"" + content_name + "\""
+      " creator=\"initiator\""
+      ">"
+      "<transport"
+      " xmlns=\"" + transport_type + "\""
+      ">"
+      + content +
+      "</transport>"
+      "</content>";
+}
+
+std::string GingleInitiateXml(const std::string& content_type) {
+  return GingleSessionXml(
+      "initiate",
+      GingleDescriptionXml(content_type));
+}
+
+std::string JingleInitiateXml(const std::string& content_name_a,
+                              const std::string& content_type_a,
+                              const std::string& content_name_b,
+                              const std::string& content_type_b) {
+  if (content_name_b.empty()) {
+    return JingleInitiateActionXml(
+        JingleEmptyContentXml(
+            content_name_a, content_type_a, kTransportType));
+  } else {
+    return JingleInitiateActionXml(
+        JingleEmptyContentXml(
+            content_name_a, content_type_a, kTransportType) +
+        JingleEmptyContentXml(
+            content_name_b, content_type_b, kTransportType));
+  }
+}
+
+std::string GingleAcceptXml(const std::string& content_type) {
+  return GingleSessionXml(
+      "accept",
+      GingleDescriptionXml(content_type));
+}
+
+std::string JingleAcceptXml(const std::string& content_name_a,
+                            const std::string& content_type_a,
+                            const std::string& content_name_b,
+                            const std::string& content_type_b) {
+  if (content_name_b.empty()) {
+    return JingleActionXml(
+        "session-accept",
+        JingleEmptyContentXml(
+            content_name_a, content_type_a, kTransportType));
+  } else {
+    return JingleActionXml(
+        "session-accept",
+        JingleEmptyContentXml(
+            content_name_a, content_type_a, kTransportType) +
+        JingleEmptyContentXml(
+            content_name_b, content_type_b, kTransportType));
+  }
+}
+
+std::string Gingle2CandidatesXml(const std::string& channel_name,
+                                 int port_index0,
+                                 int port_index1) {
+  return GingleSessionXml(
+      "candidates",
+      P2pCandidateXml(channel_name, port_index0) +
+      P2pCandidateXml(channel_name, port_index1));
+}
+
+std::string Gingle4CandidatesXml(const std::string& channel_name_a,
+                                 int port_index0,
+                                 int port_index1,
+                                 const std::string& channel_name_b,
+                                 int port_index2,
+                                 int port_index3) {
+  return GingleSessionXml(
+      "candidates",
+      P2pCandidateXml(channel_name_a, port_index0) +
+      P2pCandidateXml(channel_name_a, port_index1) +
+      P2pCandidateXml(channel_name_b, port_index2) +
+      P2pCandidateXml(channel_name_b, port_index3));
+}
+
+std::string Jingle2TransportInfoXml(const std::string& content_name,
+                                    const std::string& channel_name,
+                                    int port_index0,
+                                    int port_index1) {
+  return JingleActionXml(
+      "transport-info",
+      JingleTransportContentXml(
+          content_name, kTransportType,
+          P2pCandidateXml(channel_name, port_index0) +
+          P2pCandidateXml(channel_name, port_index1)));
+}
+
+std::string Jingle4TransportInfoXml(const std::string& content_name,
+                                    const std::string& channel_name_a,
+                                    int port_index0,
+                                    int port_index1,
+                                    const std::string& channel_name_b,
+                                    int port_index2,
+                                    int port_index3) {
+  return JingleActionXml(
+      "transport-info",
+      JingleTransportContentXml(
+          content_name, kTransportType,
+          P2pCandidateXml(channel_name_a, port_index0) +
+          P2pCandidateXml(channel_name_a, port_index1) +
+          P2pCandidateXml(channel_name_b, port_index2) +
+          P2pCandidateXml(channel_name_b, port_index3)));
+}
+
+std::string JingleDescriptionInfoXml(const std::string& content_name,
+                                     const std::string& content_type) {
+  return JingleActionXml(
+      "description-info",
+      JingleContentXml(content_name, content_type, "", ""));
+}
+
+std::string GingleRejectXml(const std::string& reason) {
+  return GingleSessionXml(
+      "reject",
+      "<" + reason + "/>");
+}
+
+std::string JingleTerminateXml(const std::string& reason) {
+    return JingleActionXml(
+        "session-terminate",
+        "<reason><" + reason + "/></reason>");
+}
+
+std::string GingleTerminateXml(const std::string& reason) {
+  return GingleSessionXml(
+      "terminate",
+      "<" + reason + "/>");
+}
+
+std::string GingleRedirectXml(const std::string& intitiate,
+                              const std::string& target) {
+  return intitiate +
+    "<error code=\"302\" type=\"modify\">"
+    "<redirect xmlns=\"http://www.google.com/session\">"
+    "xmpp:" + target +
+    "</redirect>"
+    "</error>";
+}
+
+std::string JingleRedirectXml(const std::string& intitiate,
+                              const std::string& target) {
+  return intitiate +
+    "<error code=\"302\" type=\"modify\">"
+    "<redirect xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">"
+    "xmpp:" + target +
+    "</redirect>"
+    "</error>";
+}
+
+std::string InitiateXml(SignalingProtocol protocol,
+                        const std::string& gingle_content_type,
+                        const std::string& content_name_a,
+                        const std::string& content_type_a,
+                        const std::string& content_name_b,
+                        const std::string& content_type_b) {
+  switch (protocol) {
+    case PROTOCOL_JINGLE:
+      return JingleInitiateXml(content_name_a, content_type_a,
+                               content_name_b, content_type_b);
+    case PROTOCOL_GINGLE:
+      return GingleInitiateXml(gingle_content_type);
+    case PROTOCOL_HYBRID:
+      return JingleInitiateXml(content_name_a, content_type_a,
+                               content_name_b, content_type_b) +
+          GingleInitiateXml(gingle_content_type);
+  }
+  return "";
+}
+
+std::string InitiateXml(SignalingProtocol protocol,
+                        const std::string& content_name,
+                        const std::string& content_type) {
+  return InitiateXml(protocol,
+                     content_type,
+                     content_name, content_type,
+                     "", "");
+}
+
+std::string AcceptXml(SignalingProtocol protocol,
+                      const std::string& gingle_content_type,
+                      const std::string& content_name_a,
+                      const std::string& content_type_a,
+                      const std::string& content_name_b,
+                      const std::string& content_type_b) {
+  switch (protocol) {
+    case PROTOCOL_JINGLE:
+      return JingleAcceptXml(content_name_a, content_type_a,
+                             content_name_b, content_type_b);
+    case PROTOCOL_GINGLE:
+      return GingleAcceptXml(gingle_content_type);
+    case PROTOCOL_HYBRID:
+      return
+          JingleAcceptXml(content_name_a, content_type_a,
+                          content_name_b, content_type_b) +
+          GingleAcceptXml(gingle_content_type);
+  }
+  return "";
+}
+
+
+std::string AcceptXml(SignalingProtocol protocol,
+                      const std::string& content_name,
+                      const std::string& content_type) {
+  return AcceptXml(protocol,
+                   content_type,
+                   content_name, content_type,
+                   "", "");
+}
+
+std::string TransportInfo2Xml(SignalingProtocol protocol,
+                              const std::string& content_name,
+                              const std::string& channel_name,
+                              int port_index0,
+                              int port_index1) {
+  switch (protocol) {
+    case PROTOCOL_JINGLE:
+      return Jingle2TransportInfoXml(
+          content_name,
+          channel_name, port_index0, port_index1);
+    case PROTOCOL_GINGLE:
+      return Gingle2CandidatesXml(
+          channel_name, port_index0, port_index1);
+    case PROTOCOL_HYBRID:
+      return
+          Jingle2TransportInfoXml(
+              content_name,
+              channel_name, port_index0, port_index1) +
+          Gingle2CandidatesXml(
+              channel_name, port_index0, port_index1);
+  }
+  return "";
+}
+
+std::string TransportInfo4Xml(SignalingProtocol protocol,
+                              const std::string& content_name,
+                              const std::string& channel_name_a,
+                              int port_index0,
+                              int port_index1,
+                              const std::string& channel_name_b,
+                              int port_index2,
+                              int port_index3) {
+  switch (protocol) {
+    case PROTOCOL_JINGLE:
+      return Jingle4TransportInfoXml(
+          content_name,
+          channel_name_a, port_index0, port_index1,
+          channel_name_b, port_index2, port_index3);
+    case PROTOCOL_GINGLE:
+      return Gingle4CandidatesXml(
+          channel_name_a, port_index0, port_index1,
+          channel_name_b, port_index2, port_index3);
+    case PROTOCOL_HYBRID:
+      return
+          Jingle4TransportInfoXml(
+              content_name,
+              channel_name_a, port_index0, port_index1,
+              channel_name_b, port_index2, port_index3) +
+          Gingle4CandidatesXml(
+              channel_name_a, port_index0, port_index1,
+              channel_name_b, port_index2, port_index3);
+  }
+  return "";
+}
+
+std::string RejectXml(SignalingProtocol protocol,
+                      const std::string& reason) {
+  switch (protocol) {
+    case PROTOCOL_JINGLE:
+      return JingleTerminateXml(reason);
+    case PROTOCOL_GINGLE:
+      return GingleRejectXml(reason);
+    case PROTOCOL_HYBRID:
+      return JingleTerminateXml(reason) +
+          GingleRejectXml(reason);
+  }
+  return "";
+}
+
+std::string TerminateXml(SignalingProtocol protocol,
+                         const std::string& reason) {
+  switch (protocol) {
+    case PROTOCOL_JINGLE:
+      return JingleTerminateXml(reason);
+    case PROTOCOL_GINGLE:
+      return GingleTerminateXml(reason);
+    case PROTOCOL_HYBRID:
+      return JingleTerminateXml(reason) +
+          GingleTerminateXml(reason);
+  }
+  return "";
+}
+
+std::string RedirectXml(SignalingProtocol protocol,
+                        const std::string& initiate,
+                        const std::string& target) {
+  switch (protocol) {
+    case PROTOCOL_JINGLE:
+      return JingleRedirectXml(initiate, target);
+    case PROTOCOL_GINGLE:
+      return GingleRedirectXml(initiate, target);
+  }
+  return "";
+}
+
+// TODO: Break out and join with fakeportallocator.h
+class TestPortAllocatorSession : public cricket::PortAllocatorSession {
+ public:
+  TestPortAllocatorSession(const std::string& name,
+                           const std::string& session_type,
+                           const int port_offset)
+      : PortAllocatorSession(name, session_type, 0),
+        port_offset_(port_offset),
+        ports_(kNumPorts),
+        address_("127.0.0.1", 0),
+        network_("network", "unittest", address_.ipaddr()),
+        socket_factory_(talk_base::Thread::Current()),
+        running_(false),
+        port_(28653) {
+  }
+
+  ~TestPortAllocatorSession() {
+    for (size_t i = 0; i < ports_.size(); i++)
+      delete ports_[i];
+  }
+
+  virtual void GetInitialPorts() {
+    for (int i = 0; i < kNumPorts; i++) {
+      int index = port_offset_ + i;
+      ports_[i] = cricket::UDPPort::Create(
+          talk_base::Thread::Current(), &socket_factory_,
+          &network_, address_.ipaddr(), GetPort(index), GetPort(index));
+      ports_[i]->set_username_fragment(GetUsername(index));
+      ports_[i]->set_password(GetPassword(index));
+      AddPort(ports_[i]);
+    }
+  }
+
+  virtual void StartGetAllPorts() { running_ = true; }
+  virtual void StopGetAllPorts() { running_ = false; }
+  virtual bool IsGettingAllPorts() { return running_; }
+
+  void AddPort(cricket::Port* port) {
+    port->set_name(name_);
+    port->set_preference(1.0);
+    port->set_generation(0);
+    port->SignalDestroyed.connect(
+        this, &TestPortAllocatorSession::OnPortDestroyed);
+    port->SignalAddressReady.connect(
+        this, &TestPortAllocatorSession::OnAddressReady);
+    port->PrepareAddress();
+    SignalPortReady(this, port);
+  }
+
+  void OnPortDestroyed(cricket::Port* port) {
+    for (size_t i = 0; i < ports_.size(); i++) {
+      if (ports_[i] == port)
+        ports_[i] = NULL;
+    }
+  }
+
+  void OnAddressReady(cricket::Port* port) {
+    SignalCandidatesReady(this, port->candidates());
+  }
+
+ private:
+  int port_offset_;
+  std::vector<cricket::Port*> ports_;
+  talk_base::SocketAddress address_;
+  talk_base::Network network_;
+  talk_base::BasicPacketSocketFactory socket_factory_;
+  bool running_;
+  int port_;
+};
+
+class TestPortAllocator : public cricket::PortAllocator {
+ public:
+  TestPortAllocator() : port_offset_(0) {}
+
+  virtual cricket::PortAllocatorSession*
+  CreateSession(const std::string &name,
+                const std::string &content_type) {
+    port_offset_ += 2;
+    return new TestPortAllocatorSession(name, content_type, port_offset_ - 2);
+  }
+
+  int port_offset_;
+};
+
+class TestContentDescription : public cricket::ContentDescription {
+ public:
+  explicit TestContentDescription(const std::string& gingle_content_type,
+                                  const std::string& content_type)
+      : gingle_content_type(gingle_content_type),
+        content_type(content_type) {
+  }
+
+  std::string gingle_content_type;
+  std::string content_type;
+};
+
+cricket::SessionDescription* NewTestSessionDescription(
+    const std::string gingle_content_type,
+    const std::string& content_name_a, const std::string& content_type_a,
+    const std::string& content_name_b, const std::string& content_type_b) {
+
+  cricket::SessionDescription* offer = new cricket::SessionDescription();
+  offer->AddContent(content_name_a, content_type_a,
+                    new TestContentDescription(gingle_content_type,
+                                               content_type_a));
+  if (content_name_a != content_name_b) {
+    offer->AddContent(content_name_b, content_type_b,
+                      new TestContentDescription(gingle_content_type,
+                                                 content_type_b));
+  }
+  return offer;
+}
+
+cricket::SessionDescription* NewTestSessionDescription(
+    const std::string& content_name, const std::string& content_type) {
+
+  cricket::SessionDescription* offer = new cricket::SessionDescription();
+  offer->AddContent(content_name, content_type,
+                    new TestContentDescription(content_type,
+                                               content_type));
+  return offer;
+}
+
+struct TestSessionClient: public cricket::SessionClient,
+                          public sigslot::has_slots<> {
+ public:
+  TestSessionClient() {
+  }
+
+  ~TestSessionClient() {
+  }
+
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            const cricket::ContentDescription** content,
+                            cricket::ParseError* error) {
+    std::string content_type;
+    std::string gingle_content_type;
+    if (protocol == PROTOCOL_GINGLE) {
+      gingle_content_type = elem->Name().Namespace();
+    } else {
+      content_type = elem->Name().Namespace();
+    }
+
+    *content = new TestContentDescription(gingle_content_type, content_type);
+    return true;
+  }
+
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const cricket::ContentDescription* untyped_content,
+                            buzz::XmlElement** elem,
+                            cricket::WriteError* error) {
+    const TestContentDescription* content =
+        static_cast<const TestContentDescription*>(untyped_content);
+    std::string content_type = (protocol == PROTOCOL_GINGLE ?
+                                content->gingle_content_type :
+                                content->content_type);
+     *elem = new buzz::XmlElement(
+        buzz::QName(content_type, "description"), true);
+    return true;
+  }
+
+  void OnSessionCreate(cricket::Session* session, bool initiate) {
+  }
+
+  void OnSessionDestroy(cricket::Session* session) {
+  }
+};
+
+struct ChannelHandler : sigslot::has_slots<> {
+  explicit ChannelHandler(cricket::TransportChannel* p)
+    : channel(p), last_readable(false), last_writable(false), data_count(0),
+      last_size(0) {
+    p->SignalReadableState.connect(this, &ChannelHandler::OnReadableState);
+    p->SignalWritableState.connect(this, &ChannelHandler::OnWritableState);
+    p->SignalReadPacket.connect(this, &ChannelHandler::OnReadPacket);
+  }
+
+  bool writable() const {
+    return last_writable && channel->writable();
+  }
+
+  bool readable() const {
+    return last_readable && channel->readable();
+  }
+
+  void OnReadableState(cricket::TransportChannel* p) {
+    EXPECT_EQ(channel, p);
+    last_readable = channel->readable();
+  }
+
+  void OnWritableState(cricket::TransportChannel* p) {
+    EXPECT_EQ(channel, p);
+    last_writable = channel->writable();
+  }
+
+  void OnReadPacket(cricket::TransportChannel* p, const char* buf,
+                    size_t size) {
+    EXPECT_EQ(channel, p);
+    EXPECT_LE(size, sizeof(last_data));
+    data_count += 1;
+    last_size = size;
+    std::memcpy(last_data, buf, size);
+  }
+
+  void Send(const char* data, size_t size) {
+    int result = channel->SendPacket(data, size);
+    EXPECT_EQ(static_cast<int>(size), result);
+  }
+
+  cricket::TransportChannel* channel;
+  bool last_readable, last_writable;
+  int data_count;
+  char last_data[4096];
+  size_t last_size;
+};
+
+void PrintStanza(const std::string& message,
+                 const buzz::XmlElement* stanza) {
+  printf("%s: %s\n", message.c_str(), stanza->Str().c_str());
+}
+
+class TestClient : public sigslot::has_slots<> {
+ public:
+  TestClient(cricket::PortAllocator* port_allocator,
+             int* next_message_id,
+             const std::string& local_name,
+             SignalingProtocol start_protocol,
+             const std::string& content_type,
+             const std::string& content_name_a,
+             const std::string& channel_name_a,
+             const std::string& content_name_b,
+             const std::string& channel_name_b) {
+    Construct(port_allocator, next_message_id, local_name, start_protocol,
+              content_type, content_name_a, channel_name_a, std::string(""),
+              content_name_b, channel_name_b, std::string(""));
+  }
+
+  TestClient(cricket::PortAllocator* port_allocator,
+             int* next_message_id,
+             const std::string& local_name,
+             SignalingProtocol start_protocol,
+             const std::string& content_type,
+             const std::string& content_name_a,
+             const std::string& channel_name_a,
+             const std::string& channel_name_aa,
+             const std::string& content_name_b,
+             const std::string& channel_name_b,
+             const std::string& channel_name_bb) {
+    Construct(port_allocator, next_message_id, local_name, start_protocol,
+              content_type, content_name_a, channel_name_a, channel_name_aa,
+              content_name_b, channel_name_b, channel_name_bb);
+  }
+
+  ~TestClient() {
+    if (session) {
+      session_manager->DestroySession(session);
+      EXPECT_EQ(1U, session_destroyed_count);
+    }
+    delete session_manager;
+    delete client;
+  }
+
+  void Construct(cricket::PortAllocator* pa,
+                 int* message_id,
+                 const std::string& lname,
+                 SignalingProtocol protocol,
+                 const std::string& cont_type,
+                 const std::string& cont_name_a,
+                 const std::string& chan_name_a,
+                 const std::string& chan_name_aa,
+                 const std::string& cont_name_b,
+                 const std::string& chan_name_b,
+                 const std::string& chan_name_bb) {
+    port_allocator_ = pa;
+    next_message_id = message_id;
+    local_name = lname;
+    start_protocol = protocol;
+    content_type = cont_type;
+    content_name_a = cont_name_a;
+    channel_name_a = chan_name_a;
+    channel_name_aa = chan_name_aa;
+    content_name_b = cont_name_b;
+    channel_name_b = chan_name_b;
+    channel_name_bb = chan_name_bb;
+    session_created_count = 0;
+    session_destroyed_count = 0;
+    session_remote_description_update_count = 0;
+    last_expected_sent_stanza = NULL;
+    session = NULL;
+    last_session_state = cricket::BaseSession::STATE_INIT;
+    chan_a = NULL;
+    chan_b = NULL;
+    blow_up_on_error = true;
+    error_count = 0;
+    if (!chan_name_aa.empty() || !chan_name_bb.empty()) {
+      chan_aa = NULL;
+      chan_bb = NULL;
+    }
+
+    session_manager = new cricket::SessionManager(port_allocator_);
+    session_manager->SignalSessionCreate.connect(
+        this, &TestClient::OnSessionCreate);
+    session_manager->SignalSessionDestroy.connect(
+        this, &TestClient::OnSessionDestroy);
+    session_manager->SignalOutgoingMessage.connect(
+        this, &TestClient::OnOutgoingMessage);
+
+    client = new TestSessionClient();
+    session_manager->AddClient(content_type, client);
+    EXPECT_EQ(client, session_manager->GetClient(content_type));
+  }
+
+  uint32 sent_stanza_count() const {
+    return sent_stanzas.size();
+  }
+
+  const buzz::XmlElement* stanza() const {
+    return last_expected_sent_stanza;
+  }
+
+  cricket::BaseSession::State session_state() const {
+    EXPECT_EQ(last_session_state, session->state());
+    return session->state();
+  }
+
+  void SetSessionState(cricket::BaseSession::State state) {
+    session->SetState(state);
+    EXPECT_EQ_WAIT(last_session_state, session->state(), kEventTimeout);
+  }
+
+  void CreateSession() {
+    session_manager->CreateSession(local_name, content_type);
+  }
+
+  void DeliverStanza(const buzz::XmlElement* stanza) {
+    session_manager->OnIncomingMessage(stanza);
+  }
+
+  void DeliverStanza(const std::string& str) {
+    buzz::XmlElement* stanza = buzz::XmlElement::ForStr(str);
+    session_manager->OnIncomingMessage(stanza);
+    delete stanza;
+  }
+
+  void DeliverAckToLastStanza() {
+    const buzz::XmlElement* orig_stanza = stanza();
+    const buzz::XmlElement* response_stanza =
+        buzz::XmlElement::ForStr(IqAck(orig_stanza->Attr(buzz::QN_IQ), "", ""));
+    session_manager->OnIncomingResponse(orig_stanza, response_stanza);
+    delete response_stanza;
+  }
+
+  void ExpectSentStanza(const std::string& expected) {
+    EXPECT_TRUE(!sent_stanzas.empty()) <<
+        "Found no stanza when expected " << expected;
+
+    last_expected_sent_stanza = sent_stanzas.front();
+    sent_stanzas.pop_front();
+
+    std::string actual = last_expected_sent_stanza->Str();
+    EXPECT_EQ(expected, actual);
+  }
+
+  void SkipUnsentStanza() {
+    GetNextOutgoingMessageID();
+  }
+
+  bool HasTransport(const std::string& content_name) const {
+    ASSERT(session != NULL);
+    const cricket::Transport* transport = session->GetTransport(content_name);
+    return transport != NULL && (kTransportType == transport->type());
+  }
+
+  bool HasChannel(const std::string& content_name,
+                  const std::string& channel_name) const {
+    ASSERT(session != NULL);
+    const cricket::TransportChannel* channel =
+        session->GetChannel(content_name, channel_name);
+    return channel != NULL && (channel_name == channel->name());
+  }
+
+  cricket::TransportChannel* GetChannel(const std::string& content_name,
+                                        const std::string& channel_name) const {
+    ASSERT(session != NULL);
+    return session->GetChannel(content_name, channel_name);
+  }
+
+  void OnSessionCreate(cricket::Session* created_session, bool initiate) {
+    session_created_count += 1;
+
+    session = created_session;
+    session->set_current_protocol(start_protocol);
+    session->set_allow_local_ips(true);
+    session->SignalState.connect(this, &TestClient::OnSessionState);
+    session->SignalError.connect(this, &TestClient::OnSessionError);
+    session->SignalRemoteDescriptionUpdate.connect(
+        this, &TestClient::OnSessionRemoteDescriptionUpdate);
+
+    CreateChannels();
+  }
+
+  void OnSessionDestroy(cricket::Session *session) {
+    session_destroyed_count += 1;
+  }
+
+  void OnSessionState(cricket::BaseSession* session,
+                      cricket::BaseSession::State state) {
+    // EXPECT_EQ does not allow use of this, hence the tmp variable.
+    cricket::BaseSession* tmp = this->session;
+    EXPECT_EQ(tmp, session);
+    last_session_state = state;
+  }
+
+  void OnSessionError(cricket::BaseSession* session,
+                      cricket::BaseSession::Error error) {
+    // EXPECT_EQ does not allow use of this, hence the tmp variable.
+    cricket::BaseSession* tmp = this->session;
+    EXPECT_EQ(tmp, session);
+    if (blow_up_on_error) {
+      EXPECT_TRUE(false);
+    } else {
+      error_count++;
+    }
+  }
+
+  void OnSessionRemoteDescriptionUpdate(cricket::BaseSession* session,
+      const cricket::ContentInfos& contents) {
+    session_remote_description_update_count++;
+  }
+
+  void PrepareCandidates() {
+    session_manager->OnSignalingReady();
+  }
+
+  void OnOutgoingMessage(cricket::SessionManager* manager,
+                         const buzz::XmlElement* stanza) {
+    buzz::XmlElement* elem = new buzz::XmlElement(*stanza);
+    EXPECT_TRUE(elem->Name() == buzz::QN_IQ);
+    EXPECT_TRUE(elem->HasAttr(buzz::QN_TO));
+    EXPECT_FALSE(elem->HasAttr(buzz::QN_FROM));
+    EXPECT_TRUE(elem->HasAttr(buzz::QN_TYPE));
+    EXPECT_TRUE((elem->Attr(buzz::QN_TYPE) == "set") ||
+                (elem->Attr(buzz::QN_TYPE) == "result") ||
+                (elem->Attr(buzz::QN_TYPE) == "error"));
+
+    elem->SetAttr(buzz::QN_FROM, local_name);
+    if (elem->Attr(buzz::QN_TYPE) == "set") {
+      EXPECT_FALSE(elem->HasAttr(buzz::QN_ID));
+      elem->SetAttr(buzz::QN_ID, GetNextOutgoingMessageID());
+    }
+
+    // Uncommenting this is useful for debugging.
+    // PrintStanza("OutgoingMessage", elem);
+    sent_stanzas.push_back(elem);
+  }
+
+  std::string GetNextOutgoingMessageID() {
+    int message_id = (*next_message_id)++;
+    std::ostringstream ost;
+    ost << message_id;
+    return ost.str();
+  }
+
+  void CreateChannels() {
+    ASSERT(session != NULL);
+    chan_a = new ChannelHandler(
+        session->CreateChannel(content_name_a, channel_name_a));
+    chan_b = new ChannelHandler(
+        session->CreateChannel(content_name_b, channel_name_b));
+    if (!channel_name_aa.empty() && !channel_name_bb.empty()) {
+      chan_aa = new ChannelHandler(
+          session->CreateChannel(content_name_a, channel_name_aa));
+      chan_bb = new ChannelHandler(
+          session->CreateChannel(content_name_b, channel_name_bb));
+    }
+  }
+
+  int* next_message_id;
+  std::string local_name;
+  SignalingProtocol start_protocol;
+  std::string content_type;
+  std::string content_name_a;
+  std::string channel_name_a;
+  std::string channel_name_aa;
+  std::string content_name_b;
+  std::string channel_name_b;
+  std::string channel_name_bb;
+
+  uint32 session_created_count;
+  uint32 session_destroyed_count;
+  uint32 session_remote_description_update_count;
+  std::deque<buzz::XmlElement*> sent_stanzas;
+  buzz::XmlElement* last_expected_sent_stanza;
+
+  cricket::SessionManager* session_manager;
+  TestSessionClient* client;
+  cricket::PortAllocator* port_allocator_;
+  cricket::Session* session;
+  cricket::BaseSession::State last_session_state;
+  ChannelHandler* chan_a;
+  ChannelHandler* chan_aa;
+  ChannelHandler* chan_b;
+  ChannelHandler* chan_bb;
+  bool blow_up_on_error;
+  int error_count;
+};
+
+class SessionTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    // Seed needed for each test to satisfy expectations.
+    talk_base::SetRandomTestMode(true);
+  }
+
+  virtual void TearDown() {
+    talk_base::SetRandomTestMode(false);
+  }
+
+  // Tests sending data between two clients, over two channels.
+  void TestSendRecv(ChannelHandler* chan1a,
+                    ChannelHandler* chan1b,
+                    ChannelHandler* chan2a,
+                    ChannelHandler* chan2b) {
+    const char* dat1a = "spamspamspamspamspamspamspambakedbeansspam";
+    const char* dat2a = "mapssnaebdekabmapsmapsmapsmapsmapsmapsmaps";
+    const char* dat1b = "Lobster Thermidor a Crevette with a mornay sauce...";
+    const char* dat2b = "...ecuas yanrom a htiw etteverC a rodimrehT retsboL";
+
+    for (int i = 0; i < 20; i++) {
+      chan1a->Send(dat1a, strlen(dat1a));
+      chan1b->Send(dat1b, strlen(dat1b));
+      chan2a->Send(dat2a, strlen(dat2a));
+      chan2b->Send(dat2b, strlen(dat2b));
+
+      EXPECT_EQ_WAIT(i + 1, chan1a->data_count, kEventTimeout);
+      EXPECT_EQ_WAIT(i + 1, chan1b->data_count, kEventTimeout);
+      EXPECT_EQ_WAIT(i + 1, chan2a->data_count, kEventTimeout);
+      EXPECT_EQ_WAIT(i + 1, chan2b->data_count, kEventTimeout);
+
+      EXPECT_EQ(strlen(dat2a), chan1a->last_size);
+      EXPECT_EQ(strlen(dat2b), chan1b->last_size);
+      EXPECT_EQ(strlen(dat1a), chan2a->last_size);
+      EXPECT_EQ(strlen(dat1b), chan2b->last_size);
+
+      EXPECT_EQ(0, std::memcmp(chan1a->last_data, dat2a,
+                               strlen(dat2a)));
+      EXPECT_EQ(0, std::memcmp(chan1b->last_data, dat2b,
+                               strlen(dat2b)));
+      EXPECT_EQ(0, std::memcmp(chan2a->last_data, dat1a,
+                               strlen(dat1a)));
+      EXPECT_EQ(0, std::memcmp(chan2b->last_data, dat1b,
+                               strlen(dat1b)));
+    }
+  }
+
+  // Test an initiate from one client to another, each with
+  // independent initial protocols.  Checks for the correct initiates,
+  // candidates, and accept messages, and tests that working network
+  // channels are established.
+  void TestSession(SignalingProtocol initiator_protocol,
+                   SignalingProtocol responder_protocol,
+                   SignalingProtocol resulting_protocol,
+                   const std::string& gingle_content_type,
+                   const std::string& content_type,
+                   const std::string& content_name_a,
+                   const std::string& channel_name_a,
+                   const std::string& content_name_b,
+                   const std::string& channel_name_b,
+                   const std::string& initiate_xml,
+                   const std::string& transport_info_a_xml,
+                   const std::string& transport_info_b_xml,
+                   const std::string& transport_info_reply_a_xml,
+                   const std::string& transport_info_reply_b_xml,
+                   const std::string& accept_xml) {
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, initiator_protocol,
+                       content_type,
+                       content_name_a,  channel_name_a,
+                       content_name_b,  channel_name_b));
+    talk_base::scoped_ptr<TestClient> responder(
+        new TestClient(allocator.get(), &next_message_id,
+                       kResponder, responder_protocol,
+                       content_type,
+                       content_name_a,  channel_name_a,
+                       content_name_b,  channel_name_b));
+
+    // Create Session and check channels and state.
+    initiator->CreateSession();
+    EXPECT_EQ(1U, initiator->session_created_count);
+    EXPECT_EQ(kSessionId, initiator->session->id());
+    EXPECT_EQ(initiator->session->local_name(), kInitiator);
+    EXPECT_EQ(cricket::BaseSession::STATE_INIT,
+              initiator->session_state());
+
+    EXPECT_TRUE(initiator->HasTransport(content_name_a));
+    EXPECT_TRUE(initiator->HasChannel(content_name_a, channel_name_a));
+    EXPECT_TRUE(initiator->HasTransport(content_name_b));
+    EXPECT_TRUE(initiator->HasChannel(content_name_b, channel_name_b));
+
+    // Initiate and expect initiate message sent.
+    cricket::SessionDescription* offer = NewTestSessionDescription(
+        gingle_content_type,
+        content_name_a, content_type,
+        content_name_b, content_type);
+    EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+    EXPECT_EQ(initiator->session->remote_name(), kResponder);
+    EXPECT_EQ(initiator->session->local_description(), offer);
+
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+              initiator->session_state());
+    initiator->ExpectSentStanza(
+        IqSet("0", kInitiator, kResponder, initiate_xml));
+
+    // Deliver the initiate. Expect ack and session created with
+    // transports.
+    responder->DeliverStanza(initiator->stanza());
+    responder->ExpectSentStanza(
+        IqAck("0", kResponder, kInitiator));
+    EXPECT_EQ(0U, responder->sent_stanza_count());
+
+    EXPECT_EQ(1U, responder->session_created_count);
+    EXPECT_EQ(kSessionId, responder->session->id());
+    EXPECT_EQ(responder->session->local_name(), kResponder);
+    EXPECT_EQ(responder->session->remote_name(), kInitiator);
+    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+              responder->session_state());
+
+    EXPECT_TRUE(responder->HasTransport(content_name_a));
+    EXPECT_TRUE(responder->HasChannel(content_name_a, channel_name_a));
+    EXPECT_TRUE(responder->HasTransport(content_name_b));
+    EXPECT_TRUE(responder->HasChannel(content_name_b, channel_name_b));
+
+    // Expect transport-info message from initiator.
+    // But don't send candidates until initiate ack is received.
+    initiator->PrepareCandidates();
+    WAIT(initiator->sent_stanza_count() > 0, 100);
+    EXPECT_EQ(0U, initiator->sent_stanza_count());
+    initiator->DeliverAckToLastStanza();
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    initiator->ExpectSentStanza(
+        IqSet("1", kInitiator, kResponder, transport_info_a_xml));
+
+    // Deliver transport-info and expect ack.
+    responder->DeliverStanza(initiator->stanza());
+    responder->ExpectSentStanza(
+        IqAck("1", kResponder, kInitiator));
+
+    if (!transport_info_b_xml.empty()) {
+      // Expect second transport-info message from initiator.
+      EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+      initiator->ExpectSentStanza(
+          IqSet("2", kInitiator, kResponder, transport_info_b_xml));
+      EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+      // Deliver second transport-info message and expect ack.
+      responder->DeliverStanza(initiator->stanza());
+      responder->ExpectSentStanza(
+          IqAck("2", kResponder, kInitiator));
+    } else {
+      EXPECT_EQ(0U, initiator->sent_stanza_count());
+      EXPECT_EQ(0U, responder->sent_stanza_count());
+      initiator->SkipUnsentStanza();
+    }
+
+    // Expect reply transport-info message from responder.
+    responder->PrepareCandidates();
+    EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+    responder->ExpectSentStanza(
+        IqSet("3", kResponder, kInitiator, transport_info_reply_a_xml));
+
+    // Deliver reply transport-info and expect ack.
+    initiator->DeliverStanza(responder->stanza());
+    initiator->ExpectSentStanza(
+        IqAck("3", kInitiator, kResponder));
+
+    if (!transport_info_reply_b_xml.empty()) {
+      // Expect second reply transport-info message from responder.
+      EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+      responder->ExpectSentStanza(
+          IqSet("4", kResponder, kInitiator, transport_info_reply_b_xml));
+      EXPECT_EQ(0U, responder->sent_stanza_count());
+
+      // Deliver second reply transport-info message and expect ack.
+      initiator->DeliverStanza(responder->stanza());
+      initiator->ExpectSentStanza(
+          IqAck("4", kInitiator, kResponder));
+      EXPECT_EQ(0U, initiator->sent_stanza_count());
+    } else {
+      EXPECT_EQ(0U, initiator->sent_stanza_count());
+      EXPECT_EQ(0U, responder->sent_stanza_count());
+      responder->SkipUnsentStanza();
+    }
+
+    // The channels should be able to become writable at this point.  This
+    // requires pinging, so it may take a little while.
+    EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
+                     initiator->chan_a->readable(), kEventTimeout);
+    EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
+                     initiator->chan_b->readable(), kEventTimeout);
+    EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
+                     responder->chan_a->readable(), kEventTimeout);
+    EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
+                     responder->chan_b->readable(), kEventTimeout);
+
+    // Accept the session and expect accept stanza.
+    cricket::SessionDescription* answer = NewTestSessionDescription(
+        gingle_content_type,
+        content_name_a, content_type,
+        content_name_b, content_type);
+    EXPECT_TRUE(responder->session->Accept(answer));
+    EXPECT_EQ(responder->session->local_description(), answer);
+
+    responder->ExpectSentStanza(
+        IqSet("5", kResponder, kInitiator, accept_xml));
+    EXPECT_EQ(0U, responder->sent_stanza_count());
+
+    // Deliver the accept message and expect an ack.
+    initiator->DeliverStanza(responder->stanza());
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    initiator->ExpectSentStanza(
+        IqAck("5", kInitiator, kResponder));
+    EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+    // Both sessions should be in progress and have functioning
+    // channels.
+    EXPECT_EQ(resulting_protocol, initiator->session->current_protocol());
+    EXPECT_EQ(resulting_protocol, responder->session->current_protocol());
+    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+                   initiator->session_state(), kEventTimeout);
+    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+                   responder->session_state(), kEventTimeout);
+    TestSendRecv(initiator->chan_a, initiator->chan_b,
+                 responder->chan_a, responder->chan_b);
+
+    if (resulting_protocol == PROTOCOL_JINGLE) {
+      // Deliver a description-info message to the initiator and check if the
+      // content description changes.
+      EXPECT_EQ(0U, initiator->session_remote_description_update_count);
+
+      const cricket::SessionDescription* old_session_desc =
+          initiator->session->remote_description();
+      const cricket::ContentInfo* old_content_a =
+          old_session_desc->GetContentByName(content_name_a);
+      const cricket::ContentDescription* old_content_desc_a =
+          old_content_a->description;
+      const cricket::ContentInfo* old_content_b =
+          old_session_desc->GetContentByName(content_name_b);
+      const cricket::ContentDescription* old_content_desc_b =
+          old_content_b->description;
+      EXPECT_TRUE(old_content_desc_a != NULL);
+      EXPECT_TRUE(old_content_desc_b != NULL);
+
+      LOG(LS_INFO) << "A " << old_content_a->name;
+      LOG(LS_INFO) << "B " << old_content_b->name;
+
+      std::string description_info_xml =
+          JingleDescriptionInfoXml(content_name_a, content_type);
+      initiator->DeliverStanza(
+          IqSet("6", kResponder, kInitiator, description_info_xml));
+      responder->SkipUnsentStanza();
+      EXPECT_EQ(1U, initiator->session_remote_description_update_count);
+
+      const cricket::SessionDescription* new_session_desc =
+          initiator->session->remote_description();
+      const cricket::ContentInfo* new_content_a =
+          new_session_desc->GetContentByName(content_name_a);
+      const cricket::ContentDescription* new_content_desc_a =
+          new_content_a->description;
+      const cricket::ContentInfo* new_content_b =
+          new_session_desc->GetContentByName(content_name_b);
+      const cricket::ContentDescription* new_content_desc_b =
+          new_content_b->description;
+      EXPECT_TRUE(new_content_desc_a != NULL);
+      EXPECT_TRUE(new_content_desc_b != NULL);
+
+      // TODO: We used to replace contents from an update, but
+      // that no longer works with partial updates.  We need to figure out
+      // a way to merge patial updates into contents.  For now, users of
+      // Session should listen to SignalRemoteDescriptionUpdate and handle
+      // updates.  They should not expect remote_description to be the
+      // latest value.
+      // See session.cc OnDescriptionInfoMessage.
+
+      // EXPECT_NE(old_content_desc_a, new_content_desc_a);
+
+      // if (content_name_a != content_name_b) {
+      //   // If content_name_a != content_name_b, then b's content description
+      //   // should not have changed since the description-info message only
+      //   // contained an update for content_name_a.
+      //   EXPECT_EQ(old_content_desc_b, new_content_desc_b);
+      // }
+
+      EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+      initiator->ExpectSentStanza(
+          IqAck("6", kInitiator, kResponder));
+      EXPECT_EQ(0U, initiator->sent_stanza_count());
+    } else {
+      responder->SkipUnsentStanza();
+    }
+
+    initiator->session->Terminate();
+    initiator->ExpectSentStanza(
+        IqSet("7", kInitiator, kResponder,
+              TerminateXml(resulting_protocol,
+                           cricket::STR_TERMINATE_SUCCESS)));
+
+    responder->DeliverStanza(initiator->stanza());
+    responder->ExpectSentStanza(
+        IqAck("7", kResponder, kInitiator));
+    EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE,
+              initiator->session_state());
+    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
+              responder->session_state());
+  }
+
+  // Test an initiate with other content, called "main".
+  void TestOtherContent(SignalingProtocol initiator_protocol,
+                        SignalingProtocol responder_protocol,
+                        SignalingProtocol resulting_protocol) {
+    std::string content_name = "main";
+    std::string content_type = "http://oink.splat/session";
+    std::string content_name_a = content_name;
+    std::string channel_name_a = "rtcp";
+    std::string content_name_b = content_name;
+    std::string channel_name_b = "rtp";
+    std::string initiate_xml = InitiateXml(
+        initiator_protocol,
+        content_name_a, content_type);
+    std::string transport_info_a_xml = TransportInfo4Xml(
+        initiator_protocol, content_name,
+        channel_name_a, 0, 1,
+        channel_name_b, 2, 3);
+    std::string transport_info_b_xml = "";
+    std::string transport_info_reply_a_xml = TransportInfo4Xml(
+        resulting_protocol, content_name,
+        channel_name_a, 4, 5,
+        channel_name_b, 6, 7);
+    std::string transport_info_reply_b_xml = "";
+    std::string accept_xml = AcceptXml(
+        resulting_protocol,
+        content_name_a, content_type);
+
+
+    TestSession(initiator_protocol, responder_protocol, resulting_protocol,
+                content_type,
+                content_type,
+                content_name_a, channel_name_a,
+                content_name_b, channel_name_b,
+                initiate_xml,
+                transport_info_a_xml, transport_info_b_xml,
+                transport_info_reply_a_xml, transport_info_reply_b_xml,
+                accept_xml);
+  }
+
+  // Test an initiate with audio content.
+  void TestAudioContent(SignalingProtocol initiator_protocol,
+                        SignalingProtocol responder_protocol,
+                        SignalingProtocol resulting_protocol) {
+    std::string gingle_content_type = cricket::NS_GINGLE_AUDIO;
+    std::string content_name = cricket::CN_AUDIO;
+    std::string content_type = cricket::NS_JINGLE_RTP;
+    std::string channel_name_a = "rtcp";
+    std::string channel_name_b = "rtp";
+    std::string initiate_xml = InitiateXml(
+        initiator_protocol,
+        gingle_content_type,
+        content_name, content_type,
+        "", "");
+    std::string transport_info_a_xml = TransportInfo4Xml(
+        initiator_protocol, content_name,
+        channel_name_a, 0, 1,
+        channel_name_b, 2, 3);
+    std::string transport_info_b_xml = "";
+    std::string transport_info_reply_a_xml = TransportInfo4Xml(
+        resulting_protocol, content_name,
+        channel_name_a, 4, 5,
+        channel_name_b, 6, 7);
+    std::string transport_info_reply_b_xml = "";
+    std::string accept_xml = AcceptXml(
+        resulting_protocol,
+        gingle_content_type,
+        content_name, content_type,
+        "", "");
+
+
+    TestSession(initiator_protocol, responder_protocol, resulting_protocol,
+                gingle_content_type,
+                content_type,
+                content_name, channel_name_a,
+                content_name, channel_name_b,
+                initiate_xml,
+                transport_info_a_xml, transport_info_b_xml,
+                transport_info_reply_a_xml, transport_info_reply_b_xml,
+                accept_xml);
+  }
+
+  // Since media content is "split" into two contents (audio and
+  // video), we need to treat it special.
+  void TestVideoContents(SignalingProtocol initiator_protocol,
+                         SignalingProtocol responder_protocol,
+                         SignalingProtocol resulting_protocol) {
+    std::string content_type = cricket::NS_JINGLE_RTP;
+    std::string gingle_content_type = cricket::NS_GINGLE_VIDEO;
+    std::string content_name_a = cricket::CN_AUDIO;
+    std::string channel_name_a = "rtcp";
+    std::string content_name_b = cricket::CN_VIDEO;
+    std::string channel_name_b = "video_rtp";
+
+    std::string initiate_xml = InitiateXml(
+        initiator_protocol,
+        gingle_content_type,
+        content_name_a, content_type,
+        content_name_b, content_type);
+    std::string transport_info_a_xml = TransportInfo2Xml(
+        initiator_protocol, content_name_a,
+        channel_name_a, 0, 1);
+    std::string transport_info_b_xml = TransportInfo2Xml(
+        initiator_protocol, content_name_b,
+        channel_name_b, 2, 3);
+    std::string transport_info_reply_a_xml = TransportInfo2Xml(
+        resulting_protocol, content_name_a,
+        channel_name_a, 4, 5);
+    std::string transport_info_reply_b_xml = TransportInfo2Xml(
+        resulting_protocol, content_name_b,
+        channel_name_b, 6, 7);
+    std::string accept_xml = AcceptXml(
+        resulting_protocol,
+        gingle_content_type,
+        content_name_a, content_type,
+        content_name_b, content_type);
+
+    TestSession(initiator_protocol, responder_protocol, resulting_protocol,
+                gingle_content_type,
+                content_type,
+                content_name_a, channel_name_a,
+                content_name_b, channel_name_b,
+                initiate_xml,
+                transport_info_a_xml, transport_info_b_xml,
+                transport_info_reply_a_xml, transport_info_reply_b_xml,
+                accept_xml);
+  }
+
+  void TestBadRedirect(SignalingProtocol protocol) {
+    std::string content_name = "main";
+    std::string content_type = "http://oink.splat/session";
+    std::string channel_name_a = "chana";
+    std::string channel_name_b = "chanb";
+    std::string initiate_xml = InitiateXml(
+        protocol, content_name, content_type);
+    std::string transport_info_xml = TransportInfo4Xml(
+        protocol, content_name,
+        channel_name_a, 0, 1,
+        channel_name_b, 2, 3);
+    std::string transport_info_reply_xml = TransportInfo4Xml(
+        protocol, content_name,
+        channel_name_a, 4, 5,
+        channel_name_b, 6, 7);
+    std::string accept_xml = AcceptXml(
+        protocol, content_name, content_type);
+    std::string responder_full = kResponder + "/full";
+
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, protocol,
+                       content_type,
+                       content_name, channel_name_a,
+                       content_name, channel_name_b));
+
+    talk_base::scoped_ptr<TestClient> responder(
+        new TestClient(allocator.get(), &next_message_id,
+                       responder_full, protocol,
+                       content_type,
+                       content_name,  channel_name_a,
+                       content_name,  channel_name_b));
+
+    // Create Session and check channels and state.
+    initiator->CreateSession();
+    EXPECT_EQ(1U, initiator->session_created_count);
+    EXPECT_EQ(kSessionId, initiator->session->id());
+    EXPECT_EQ(initiator->session->local_name(), kInitiator);
+    EXPECT_EQ(cricket::BaseSession::STATE_INIT,
+              initiator->session_state());
+
+    EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_a));
+    EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_b));
+
+    // Initiate and expect initiate message sent.
+    cricket::SessionDescription* offer = NewTestSessionDescription(
+        content_name, content_type);
+    EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+    EXPECT_EQ(initiator->session->remote_name(), kResponder);
+    EXPECT_EQ(initiator->session->local_description(), offer);
+
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+              initiator->session_state());
+    initiator->ExpectSentStanza(
+        IqSet("0", kInitiator, kResponder, initiate_xml));
+
+    // Expect transport-info message from initiator.
+    initiator->DeliverAckToLastStanza();
+    initiator->PrepareCandidates();
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    initiator->ExpectSentStanza(
+        IqSet("1", kInitiator, kResponder, transport_info_xml));
+
+    // Send an unauthorized redirect to the initiator and expect it be ignored.
+    initiator->blow_up_on_error = false;
+    const buzz::XmlElement* initiate_stanza = initiator->stanza();
+    talk_base::scoped_ptr<buzz::XmlElement> redirect_stanza(
+        buzz::XmlElement::ForStr(
+            IqError("ER", kResponder, kInitiator,
+                    RedirectXml(protocol, initiate_xml, "not@allowed.com"))));
+    initiator->session_manager->OnFailedSend(
+        initiate_stanza, redirect_stanza.get());
+    EXPECT_EQ(initiator->session->remote_name(), kResponder);
+    initiator->blow_up_on_error = true;
+    EXPECT_EQ(initiator->error_count, 1);
+  }
+
+  void TestGoodRedirect(SignalingProtocol protocol) {
+    std::string content_name = "main";
+    std::string content_type = "http://oink.splat/session";
+    std::string channel_name_a = "chana";
+    std::string channel_name_b = "chanb";
+    std::string initiate_xml = InitiateXml(
+        protocol, content_name, content_type);
+    std::string transport_info_xml = TransportInfo4Xml(
+        protocol, content_name,
+        channel_name_a, 0, 1,
+        channel_name_b, 2, 3);
+    std::string transport_info_reply_xml = TransportInfo4Xml(
+        protocol, content_name,
+        channel_name_a, 4, 5,
+        channel_name_b, 6, 7);
+    std::string accept_xml = AcceptXml(
+        protocol, content_name, content_type);
+    std::string responder_full = kResponder + "/full";
+
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, protocol,
+                       content_type,
+                       content_name, channel_name_a,
+                       content_name, channel_name_b));
+
+    talk_base::scoped_ptr<TestClient> responder(
+        new TestClient(allocator.get(), &next_message_id,
+                       responder_full, protocol,
+                       content_type,
+                       content_name,  channel_name_a,
+                       content_name,  channel_name_b));
+
+    // Create Session and check channels and state.
+    initiator->CreateSession();
+    EXPECT_EQ(1U, initiator->session_created_count);
+    EXPECT_EQ(kSessionId, initiator->session->id());
+    EXPECT_EQ(initiator->session->local_name(), kInitiator);
+    EXPECT_EQ(cricket::BaseSession::STATE_INIT,
+              initiator->session_state());
+
+    EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_a));
+    EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_b));
+
+    // Initiate and expect initiate message sent.
+    cricket::SessionDescription* offer = NewTestSessionDescription(
+        content_name, content_type);
+    EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+    EXPECT_EQ(initiator->session->remote_name(), kResponder);
+    EXPECT_EQ(initiator->session->local_description(), offer);
+
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+              initiator->session_state());
+    initiator->ExpectSentStanza(
+        IqSet("0", kInitiator, kResponder, initiate_xml));
+
+    // Expect transport-info message from initiator.
+    initiator->DeliverAckToLastStanza();
+    initiator->PrepareCandidates();
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    initiator->ExpectSentStanza(
+        IqSet("1", kInitiator, kResponder, transport_info_xml));
+
+    // Send a redirect to the initiator and expect all of the message
+    // to be resent.
+    const buzz::XmlElement* initiate_stanza = initiator->stanza();
+    talk_base::scoped_ptr<buzz::XmlElement> redirect_stanza(
+        buzz::XmlElement::ForStr(
+            IqError("ER2", kResponder, kInitiator,
+                    RedirectXml(protocol, initiate_xml, responder_full))));
+    initiator->session_manager->OnFailedSend(
+        initiate_stanza, redirect_stanza.get());
+    EXPECT_EQ(initiator->session->remote_name(), responder_full);
+
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    initiator->ExpectSentStanza(
+        IqSet("2", kInitiator, responder_full, initiate_xml));
+    initiator->ExpectSentStanza(
+        IqSet("3", kInitiator, responder_full, transport_info_xml));
+
+    // Deliver the initiate. Expect ack and session created with
+    // transports.
+    responder->DeliverStanza(
+        IqSet("2", kInitiator, responder_full, initiate_xml));
+    responder->ExpectSentStanza(
+        IqAck("2", responder_full, kInitiator));
+    EXPECT_EQ(0U, responder->sent_stanza_count());
+
+    EXPECT_EQ(1U, responder->session_created_count);
+    EXPECT_EQ(kSessionId, responder->session->id());
+    EXPECT_EQ(responder->session->local_name(), responder_full);
+    EXPECT_EQ(responder->session->remote_name(), kInitiator);
+    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+              responder->session_state());
+
+    EXPECT_TRUE(responder->HasChannel(content_name, channel_name_a));
+    EXPECT_TRUE(responder->HasChannel(content_name, channel_name_b));
+
+    // Deliver transport-info and expect ack.
+    responder->DeliverStanza(
+        IqSet("3", kInitiator, responder_full, transport_info_xml));
+    responder->ExpectSentStanza(
+        IqAck("3", responder_full, kInitiator));
+
+    // Expect reply transport-infos sent to new remote JID
+    responder->PrepareCandidates();
+    EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+    responder->ExpectSentStanza(
+        IqSet("4", responder_full, kInitiator, transport_info_reply_xml));
+
+    initiator->DeliverStanza(responder->stanza());
+    initiator->ExpectSentStanza(
+        IqAck("4", kInitiator, responder_full));
+
+    // The channels should be able to become writable at this point.  This
+    // requires pinging, so it may take a little while.
+    EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
+                     initiator->chan_a->readable(), kEventTimeout);
+    EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
+                     initiator->chan_b->readable(), kEventTimeout);
+    EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
+                     responder->chan_a->readable(), kEventTimeout);
+    EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
+                     responder->chan_b->readable(), kEventTimeout);
+
+    // Accept the session and expect accept stanza.
+    cricket::SessionDescription* answer = NewTestSessionDescription(
+        content_name, content_type);
+    EXPECT_TRUE(responder->session->Accept(answer));
+    EXPECT_EQ(responder->session->local_description(), answer);
+
+    responder->ExpectSentStanza(
+        IqSet("5", responder_full, kInitiator, accept_xml));
+    EXPECT_EQ(0U, responder->sent_stanza_count());
+
+    // Deliver the accept message and expect an ack.
+    initiator->DeliverStanza(responder->stanza());
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    initiator->ExpectSentStanza(
+        IqAck("5", kInitiator, responder_full));
+    EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+    // Both sessions should be in progress and have functioning
+    // channels.
+    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+                   initiator->session_state(), kEventTimeout);
+    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+                   responder->session_state(), kEventTimeout);
+    TestSendRecv(initiator->chan_a, initiator->chan_b,
+                 responder->chan_a, responder->chan_b);
+  }
+
+  void TestCandidatesInInitiateAndAccept(const std::string& test_name) {
+    std::string content_name = "main";
+    std::string content_type = "http://oink.splat/session";
+    std::string channel_name_a = "rtcp";
+    std::string channel_name_b = "rtp";
+    cricket::SignalingProtocol protocol = PROTOCOL_JINGLE;
+
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, protocol,
+                       content_type,
+                       content_name,  channel_name_a,
+                       content_name,  channel_name_b));
+
+    talk_base::scoped_ptr<TestClient> responder(
+        new TestClient(allocator.get(), &next_message_id,
+                       kResponder, protocol,
+                       content_type,
+                       content_name,  channel_name_a,
+                       content_name,  channel_name_b));
+
+    // Create Session and check channels and state.
+    initiator->CreateSession();
+    EXPECT_TRUE(initiator->HasTransport(content_name));
+    EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_a));
+    EXPECT_TRUE(initiator->HasTransport(content_name));
+    EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_b));
+
+    // Initiate and expect initiate message sent.
+    cricket::SessionDescription* offer = NewTestSessionDescription(
+        content_name, content_type);
+    EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+              initiator->session_state());
+    initiator->ExpectSentStanza(
+        IqSet("0", kInitiator, kResponder,
+              InitiateXml(protocol, content_name, content_type)));
+
+    // Fake the delivery the initiate and candidates together.
+    responder->DeliverStanza(
+        IqSet("A", kInitiator, kResponder,
+            JingleInitiateActionXml(
+                JingleContentXml(
+                    content_name, content_type, kTransportType,
+                    P2pCandidateXml(channel_name_a, 0) +
+                    P2pCandidateXml(channel_name_a, 1) +
+                    P2pCandidateXml(channel_name_b, 2) +
+                    P2pCandidateXml(channel_name_b, 3)))));
+    responder->ExpectSentStanza(
+        IqAck("A", kResponder, kInitiator));
+    EXPECT_EQ(0U, responder->sent_stanza_count());
+
+    EXPECT_EQ(1U, responder->session_created_count);
+    EXPECT_EQ(kSessionId, responder->session->id());
+    EXPECT_EQ(responder->session->local_name(), kResponder);
+    EXPECT_EQ(responder->session->remote_name(), kInitiator);
+    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+              responder->session_state());
+
+    EXPECT_TRUE(responder->HasTransport(content_name));
+    EXPECT_TRUE(responder->HasChannel(content_name, channel_name_a));
+    EXPECT_TRUE(responder->HasTransport(content_name));
+    EXPECT_TRUE(responder->HasChannel(content_name, channel_name_b));
+
+    // Expect transport-info message from initiator.
+    // But don't send candidates until initiate ack is received.
+    initiator->DeliverAckToLastStanza();
+    initiator->PrepareCandidates();
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    initiator->ExpectSentStanza(
+        IqSet("1", kInitiator, kResponder,
+              TransportInfo4Xml(protocol, content_name,
+                                channel_name_a, 0, 1,
+                                channel_name_b, 2, 3)));
+
+    responder->PrepareCandidates();
+    EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+    responder->ExpectSentStanza(
+        IqSet("2", kResponder, kInitiator,
+              TransportInfo4Xml(protocol, content_name,
+                                channel_name_a, 4, 5,
+                                channel_name_b, 6, 7)));
+
+    // Accept the session and expect accept stanza.
+    cricket::SessionDescription* answer = NewTestSessionDescription(
+        content_name, content_type);
+    EXPECT_TRUE(responder->session->Accept(answer));
+
+    responder->ExpectSentStanza(
+        IqSet("3", kResponder, kInitiator,
+              AcceptXml(protocol, content_name, content_type)));
+    EXPECT_EQ(0U, responder->sent_stanza_count());
+
+    // Fake the delivery the accept and candidates together.
+    initiator->DeliverStanza(
+        IqSet("B", kResponder, kInitiator,
+            JingleActionXml("session-accept",
+                JingleContentXml(
+                    content_name, content_type, kTransportType,
+                    P2pCandidateXml(channel_name_a, 4) +
+                    P2pCandidateXml(channel_name_a, 5) +
+                    P2pCandidateXml(channel_name_b, 6) +
+                    P2pCandidateXml(channel_name_b, 7)))));
+    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+    initiator->ExpectSentStanza(
+        IqAck("B", kInitiator, kResponder));
+    EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+    // The channels should be able to become writable at this point.  This
+    // requires pinging, so it may take a little while.
+    EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
+                     initiator->chan_a->readable(), kEventTimeout);
+    EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
+                     initiator->chan_b->readable(), kEventTimeout);
+    EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
+                     responder->chan_a->readable(), kEventTimeout);
+    EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
+                     responder->chan_b->readable(), kEventTimeout);
+
+
+    // Both sessions should be in progress and have functioning
+    // channels.
+    EXPECT_EQ(protocol, initiator->session->current_protocol());
+    EXPECT_EQ(protocol, responder->session->current_protocol());
+    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+                   initiator->session_state(), kEventTimeout);
+    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+                   responder->session_state(), kEventTimeout);
+    TestSendRecv(initiator->chan_a, initiator->chan_b,
+                 responder->chan_a, responder->chan_b);
+  }
+
+  // Tests that when an initiator terminates right after initiate,
+  // everything behaves correctly.
+  void TestEarlyTerminationFromInitiator(SignalingProtocol protocol) {
+    std::string content_name = "main";
+    std::string content_type = "http://oink.splat/session";
+
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, protocol,
+                       content_type,
+                       content_name, "a",
+                       content_name, "b"));
+
+    talk_base::scoped_ptr<TestClient> responder(
+        new TestClient(allocator.get(), &next_message_id,
+                       kResponder, protocol,
+                       content_type,
+                       content_name,  "a",
+                       content_name,  "b"));
+
+    // Send initiate
+    initiator->CreateSession();
+    EXPECT_TRUE(initiator->session->Initiate(
+        kResponder, NewTestSessionDescription(content_name, content_type)));
+    initiator->ExpectSentStanza(
+        IqSet("0", kInitiator, kResponder,
+              InitiateXml(protocol, content_name, content_type)));
+    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+              initiator->session_state());
+
+    responder->DeliverStanza(initiator->stanza());
+    responder->ExpectSentStanza(
+        IqAck("0", kResponder, kInitiator));
+    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+              responder->session_state());
+
+    initiator->session->TerminateWithReason(cricket::STR_TERMINATE_ERROR);
+    initiator->ExpectSentStanza(
+        IqSet("1", kInitiator, kResponder,
+              TerminateXml(protocol, cricket::STR_TERMINATE_ERROR)));
+    EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE,
+              initiator->session_state());
+
+    responder->DeliverStanza(initiator->stanza());
+    responder->ExpectSentStanza(
+        IqAck("1", kResponder, kInitiator));
+    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
+              responder->session_state());
+  }
+
+  // Tests that when the responder rejects, everything behaves
+  // correctly.
+  void TestRejection(SignalingProtocol protocol) {
+    std::string content_name = "main";
+    std::string content_type = "http://oink.splat/session";
+
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, protocol,
+                       content_type,
+                       content_name, "a",
+                       content_name, "b"));
+
+    // Send initiate
+    initiator->CreateSession();
+    EXPECT_TRUE(initiator->session->Initiate(
+        kResponder, NewTestSessionDescription(content_name, content_type)));
+    initiator->ExpectSentStanza(
+        IqSet("0", kInitiator, kResponder,
+              InitiateXml(protocol, content_name, content_type)));
+    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+              initiator->session_state());
+
+    initiator->DeliverStanza(
+        IqSet("1", kResponder, kInitiator,
+              RejectXml(protocol, cricket::STR_TERMINATE_ERROR)));
+    initiator->ExpectSentStanza(
+        IqAck("1", kInitiator, kResponder));
+    if (protocol == PROTOCOL_JINGLE) {
+      EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
+                initiator->session_state());
+    } else {
+      EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDREJECT,
+                initiator->session_state());
+    }
+  }
+
+  void TestTransportMux() {
+    std::string content_type = cricket::NS_JINGLE_RTP;
+    std::string gingle_content_type = cricket::NS_GINGLE_VIDEO;
+    std::string content_name_a = cricket::CN_AUDIO;
+    std::string channel_name_a = "rtp";
+    std::string channel_name_aa = "rtcp";
+    std::string content_name_b = cricket::CN_VIDEO;
+    std::string channel_name_b = "video_rtp";
+    std::string channel_name_bb = "video_rtcp";
+    cricket::SignalingProtocol protocol = PROTOCOL_JINGLE;
+
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, protocol,
+                       content_type,
+                       content_name_a, channel_name_a, channel_name_aa,
+                       content_name_b, channel_name_b, channel_name_bb));
+
+    // First creating the offer and answer session descriptions required for
+    // testing.
+    cricket::SessionDescription* offer = NewTestSessionDescription(
+        gingle_content_type,
+        content_name_a, content_type,
+        content_name_b, content_type);
+    // Add group information to the offer
+    cricket::ContentGroup group(cricket::GN_BUNDLE);
+    group.AddContentName(content_name_a);
+    group.AddContentName(content_name_b);
+    EXPECT_TRUE(group.HasContentName(content_name_a));
+    EXPECT_TRUE(group.HasContentName(content_name_b));
+    offer->AddGroup(group);
+
+    // Creating answer for the offer.
+    cricket::SessionDescription* answer = NewTestSessionDescription(
+        gingle_content_type,
+        content_name_a, content_type,
+        content_name_b, content_type);
+    // Check if group "TOGETHER" exists in the offer. If it's present then
+    // remote supports muxing.
+    EXPECT_TRUE(offer->HasGroup(cricket::GN_BUNDLE));
+    const cricket::ContentGroup* group_offer =
+        offer->GetGroupByName(cricket::GN_BUNDLE);
+    // Not creating new copy in answer, for test we can use this for test.
+    answer->AddGroup(*group_offer);
+    EXPECT_TRUE(answer->HasGroup(cricket::GN_BUNDLE));
+
+    initiator->CreateSession();
+    EXPECT_TRUE(initiator->session->Initiate(
+        kResponder, offer));
+
+    EXPECT_TRUE(initiator->HasTransport(content_name_a));
+    EXPECT_TRUE(initiator->HasChannel(content_name_a, channel_name_a));
+    EXPECT_TRUE(initiator->HasChannel(content_name_a, channel_name_aa));
+    EXPECT_TRUE(initiator->HasTransport(content_name_b));
+    EXPECT_TRUE(initiator->HasChannel(content_name_b, channel_name_b));
+    EXPECT_TRUE(initiator->HasChannel(content_name_b, channel_name_bb));
+    // This test will not create initiator and responder. Manually change
+    // session state and invoke methods.
+    initiator->PrepareCandidates();
+    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+              initiator->session_state());
+    // Now apply answer to the session and move session state to
+    // STATE_RECEIVEDACCEPT
+    initiator->session->set_remote_description(answer);
+    initiator->session->SetState(cricket::BaseSession::STATE_RECEIVEDACCEPT);
+    cricket::TransportChannel* chan_a =
+        initiator->GetChannel(content_name_a, channel_name_a);
+    cricket::TransportChannel* chan_b =
+            initiator->GetChannel(content_name_b, channel_name_b);
+    // Since we know these are TransportChannelProxy, type cast it.
+    cricket::TransportChannelProxy* proxy_chan_a =
+        static_cast<cricket::TransportChannelProxy*>(chan_a);
+    cricket::TransportChannelProxy* proxy_chan_b =
+            static_cast<cricket::TransportChannelProxy*>(chan_b);
+    EXPECT_EQ(proxy_chan_a->impl(), proxy_chan_b->impl());
+    cricket::TransportChannel* chan_aa =
+            initiator->GetChannel(content_name_a, channel_name_aa);
+        cricket::TransportChannel* chan_bb =
+                initiator->GetChannel(content_name_b, channel_name_bb);
+    cricket::TransportChannelProxy* proxy_chan_aa =
+        static_cast<cricket::TransportChannelProxy*>(chan_aa);
+    cricket::TransportChannelProxy* proxy_chan_bb =
+            static_cast<cricket::TransportChannelProxy*>(chan_bb);
+    EXPECT_EQ(proxy_chan_aa->impl(), proxy_chan_bb->impl());
+    // TODO - Add test code to send data after mux is enabled.
+  }
+};
+
+// For each of these, "X => Y = Z" means "if a client with protocol X
+// initiates to a client with protocol Y, they end up speaking protocol Z.
+
+// Gingle => Gingle = Gingle (with other content)
+TEST_F(SessionTest, GingleToGingleOtherContent) {
+  TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Gingle => Gingle = Gingle (with audio content)
+TEST_F(SessionTest, GingleToGingleAudioContent) {
+  TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Gingle => Gingle = Gingle (with video contents)
+TEST_F(SessionTest, GingleToGingleVideoContents) {
+  TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+
+// Jingle => Jingle = Jingle (with other content)
+TEST_F(SessionTest, JingleToJingleOtherContent) {
+  TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Jingle => Jingle = Jingle (with audio content)
+TEST_F(SessionTest, JingleToJingleAudioContent) {
+  TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Jingle => Jingle = Jingle (with video contents)
+TEST_F(SessionTest, JingleToJingleVideoContents) {
+  TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+
+// Hybrid => Hybrid = Jingle (with other content)
+TEST_F(SessionTest, HybridToHybridOtherContent) {
+  TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Hybrid = Jingle (with audio content)
+TEST_F(SessionTest, HybridToHybridAudioContent) {
+  TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Hybrid = Jingle (with video contents)
+TEST_F(SessionTest, HybridToHybridVideoContents) {
+  TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+
+// Gingle => Hybrid = Gingle (with other content)
+TEST_F(SessionTest, GingleToHybridOtherContent) {
+  TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
+}
+
+// Gingle => Hybrid = Gingle (with audio content)
+TEST_F(SessionTest, GingleToHybridAudioContent) {
+  TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
+}
+
+// Gingle => Hybrid = Gingle (with video contents)
+TEST_F(SessionTest, GingleToHybridVideoContents) {
+  TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
+}
+
+
+// Jingle => Hybrid = Jingle (with other content)
+TEST_F(SessionTest, JingleToHybridOtherContent) {
+  TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Jingle => Hybrid = Jingle (with audio content)
+TEST_F(SessionTest, JingleToHybridAudioContent) {
+  TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Jingle => Hybrid = Jingle (with video contents)
+TEST_F(SessionTest, JingleToHybridVideoContents) {
+  TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+
+// Hybrid => Gingle = Gingle (with other content)
+TEST_F(SessionTest, HybridToGingleOtherContent) {
+  TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Hybrid => Gingle = Gingle (with audio content)
+TEST_F(SessionTest, HybridToGingleAudioContent) {
+  TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Hybrid => Gingle = Gingle (with video contents)
+TEST_F(SessionTest, HybridToGingleVideoContents) {
+  TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+
+// Hybrid => Jingle = Jingle (with other content)
+TEST_F(SessionTest, HybridToJingleOtherContent) {
+  TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Jingle = Jingle (with audio content)
+TEST_F(SessionTest, HybridToJingleAudioContent) {
+  TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Jingle = Jingle (with video contents)
+TEST_F(SessionTest, HybridToJingleVideoContents) {
+  TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+
+TEST_F(SessionTest, GingleEarlyTerminationFromInitiator) {
+  TestEarlyTerminationFromInitiator(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleEarlyTerminationFromInitiator) {
+  TestEarlyTerminationFromInitiator(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, HybridEarlyTerminationFromInitiator) {
+  TestEarlyTerminationFromInitiator(PROTOCOL_HYBRID);
+}
+
+
+TEST_F(SessionTest, GingleRejection) {
+  TestRejection(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleRejection) {
+  TestRejection(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, GingleGoodRedirect) {
+  TestGoodRedirect(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleGoodRedirect) {
+  TestGoodRedirect(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, GingleBadRedirect) {
+  TestBadRedirect(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleBadRedirect) {
+  TestBadRedirect(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, TestCandidatesInInitiateAndAccept) {
+  TestCandidatesInInitiateAndAccept("Candidates in initiate/accept");
+}
+
+TEST_F(SessionTest, TestTransportMux) {
+  TestTransportMux();
+}
diff --git a/talk/p2p/base/sessiondescription.cc b/talk/p2p/base/sessiondescription.cc
index 6554f5a..add9236 100644
--- a/talk/p2p/base/sessiondescription.cc
+++ b/talk/p2p/base/sessiondescription.cc
@@ -53,6 +53,29 @@
   return NULL;
 }
 
+void ContentGroup::AddContentName(const std::string& content_name) {
+  content_types_.insert(content_name);
+}
+
+bool ContentGroup::RemoveContentName(const std::string& content_name) {
+  bool ret = false;
+  std::set<std::string>::iterator iter;
+  iter = content_types_.find(content_name);
+  if (iter != content_types_.end()) {
+    content_types_.erase(iter);
+    ret = true;
+  }
+  return ret;
+}
+
+bool ContentGroup::HasContentName(const std::string& content_name) const {
+  return (content_types_.find(content_name) != content_types_.end());
+}
+
+const std::string* ContentGroup::FirstContentName() const {
+  return (content_types_.begin() != content_types_.end()) ?
+      &(*content_types_.begin()) : NULL;
+}
 const ContentInfo* SessionDescription::GetContentByName(
     const std::string& name) const {
   return FindContentInfoByName(contents_, name);
@@ -63,6 +86,10 @@
   return FindContentInfoByType(contents_, type);
 }
 
+const ContentInfo* SessionDescription::FirstContent() const {
+  return (contents_.empty()) ? NULL : &(*contents_.begin());
+}
+
 void SessionDescription::AddContent(const std::string& name,
                                     const std::string& type,
                                     const ContentDescription* description) {
@@ -82,4 +109,34 @@
   return false;
 }
 
+void SessionDescription::RemoveGroupByName(const std::string& name) {
+  for (ContentGroups::iterator iter = groups_.begin();
+       iter != groups_.end(); ++iter) {
+    if (iter->semantics() == name) {
+      groups_.erase(iter);
+    }
+  }
+}
+
+bool SessionDescription::HasGroup(const std::string& name) const {
+  for (ContentGroups::const_iterator iter = groups_.begin();
+       iter != groups_.end(); ++iter) {
+    if (iter->semantics() == name) {
+      return true;
+    }
+  }
+  return false;
+}
+
+const ContentGroup* SessionDescription::GetGroupByName(
+    const std::string& name) const {
+  for (ContentGroups::const_iterator iter = groups_.begin();
+       iter != groups_.end(); ++iter) {
+    if (iter->semantics() == name) {
+      return &(*iter);
+    }
+  }
+  return NULL;
+}
+
 }  // namespace cricket
diff --git a/talk/p2p/base/sessiondescription.h b/talk/p2p/base/sessiondescription.h
index 38c902b..153fe97 100644
--- a/talk/p2p/base/sessiondescription.h
+++ b/talk/p2p/base/sessiondescription.h
@@ -28,6 +28,7 @@
 #ifndef TALK_P2P_BASE_SESSIONDESCRIPTION_H_
 #define TALK_P2P_BASE_SESSIONDESCRIPTION_H_
 
+#include <set>
 #include <string>
 #include <vector>
 
@@ -55,7 +56,28 @@
   const ContentDescription* description;
 };
 
+// This class provides a mechanism to aggregate different media contents into a
+// group. This group can also be shared with the peers in a pre-defined format.
+// GroupInfo should be populated only with the |content_name| of the
+// MediaDescription.
+class ContentGroup {
+ public:
+  explicit ContentGroup(const std::string& semantics) :
+      semantics_(semantics) {}
+  void AddContentName(const std::string& content_name);
+  bool RemoveContentName(const std::string& content_name);
+  bool HasContentName(const std::string& content_name) const;
+  const std::string* FirstContentName() const;
+  const std::string& semantics() const { return semantics_; }
+
+ private:
+  std::string semantics_;
+  std::set<std::string> content_types_;
+};
+
 typedef std::vector<ContentInfo> ContentInfos;
+typedef std::vector<ContentGroup> ContentGroups;
+
 const ContentInfo* FindContentInfoByName(
     const ContentInfos& contents, const std::string& name);
 const ContentInfo* FindContentInfoByType(
@@ -71,6 +93,7 @@
       contents_(contents) {}
   const ContentInfo* GetContentByName(const std::string& name) const;
   const ContentInfo* FirstContentByType(const std::string& type) const;
+  const ContentInfo* FirstContent() const;
   // Takes ownership of ContentDescription*.
   void AddContent(const std::string& name,
                   const std::string& type,
@@ -84,9 +107,14 @@
       delete content->description;
     }
   }
+  bool HasGroup(const std::string& name) const;
+  void AddGroup(const ContentGroup& group) { groups_.push_back(group); }
+  void RemoveGroupByName(const std::string& name);
+  const ContentGroup* GetGroupByName(const std::string& name) const;
 
  private:
   ContentInfos contents_;
+  ContentGroups groups_;
 };
 
 // Indicates whether a ContentDescription was an offer or an answer, as
diff --git a/talk/p2p/base/sessionmessages.cc b/talk/p2p/base/sessionmessages.cc
index ae2e656..96f91d1 100644
--- a/talk/p2p/base/sessionmessages.cc
+++ b/talk/p2p/base/sessionmessages.cc
@@ -73,9 +73,9 @@
   if (type == JINGLE_ACTION_TRANSPORT_ACCEPT)
     return ACTION_TRANSPORT_ACCEPT;
   if (type == JINGLE_ACTION_DESCRIPTION_INFO)
-    return ACTION_UPDATE;
+    return ACTION_DESCRIPTION_INFO;
   if (type == GINGLE_ACTION_UPDATE)
-    return ACTION_UPDATE;
+    return ACTION_DESCRIPTION_INFO;
 
   return ACTION_UNKNOWN;
 }
@@ -362,7 +362,7 @@
 }
 
 buzz::XmlElement* NewTransportElement(const std::string& name) {
-  return new buzz::XmlElement(buzz::QName(true, name, LN_TRANSPORT), true);
+  return new buzz::XmlElement(buzz::QName(name, LN_TRANSPORT), true);
 }
 
 bool WriteCandidates(SignalingProtocol protocol,
@@ -678,6 +678,7 @@
 static bool ParseContentMessage(
     SignalingProtocol protocol,
     const buzz::XmlElement* action_elem,
+    bool expect_transports,
     const ContentParserMap& content_parsers,
     const TransportParserMap& trans_parsers,
     SessionInitiate* init,
@@ -688,7 +689,8 @@
                                  &init->contents, error))
       return false;
 
-    if (!ParseGingleTransportInfos(action_elem, init->contents, trans_parsers,
+    if (expect_transports &&
+        !ParseGingleTransportInfos(action_elem, init->contents, trans_parsers,
                                    &init->transports, error))
       return false;
   } else {
@@ -696,7 +698,8 @@
                                  &init->contents, error))
       return false;
 
-    if (!ParseJingleTransportInfos(action_elem, init->contents, trans_parsers,
+    if (expect_transports &&
+        !ParseJingleTransportInfos(action_elem, init->contents, trans_parsers,
                                    &init->transports, error))
       return false;
   }
@@ -735,7 +738,8 @@
                           const TransportParserMap& trans_parsers,
                           SessionInitiate* init,
                           ParseError* error) {
-  return ParseContentMessage(protocol, action_elem,
+  bool expect_transports = true;
+  return ParseContentMessage(protocol, action_elem, expect_transports,
                              content_parsers, trans_parsers,
                              init, error);
 }
@@ -759,7 +763,8 @@
                         const TransportParserMap& transport_parsers,
                         SessionAccept* accept,
                         ParseError* error) {
-  return ParseContentMessage(protocol, action_elem,
+  bool expect_transports = true;
+  return ParseContentMessage(protocol, action_elem, expect_transports,
                              content_parsers, transport_parsers,
                              accept, error);
 }
@@ -807,13 +812,12 @@
                            const SessionTerminate& term,
                            XmlElements* elems) {
   if (protocol == PROTOCOL_GINGLE) {
-    elems->push_back(new buzz::XmlElement(
-        buzz::QName(true, NS_GINGLE, term.reason)));
+    elems->push_back(new buzz::XmlElement(buzz::QName(NS_GINGLE, term.reason)));
   } else {
     if (!term.reason.empty()) {
       buzz::XmlElement* reason_elem = new buzz::XmlElement(QN_JINGLE_REASON);
       reason_elem->AddElement(new buzz::XmlElement(
-          buzz::QName(true, NS_JINGLE, term.reason)));
+          buzz::QName(NS_JINGLE, term.reason)));
       elems->push_back(reason_elem);
     }
   }
@@ -825,7 +829,8 @@
                           const TransportParserMap& transport_parsers,
                           DescriptionInfo* description_info,
                           ParseError* error) {
-  return ParseContentMessage(protocol, action_elem,
+  bool expect_transports = false;
+  return ParseContentMessage(protocol, action_elem, expect_transports,
                              content_parsers, transport_parsers,
                              description_info, error);
 }
diff --git a/talk/p2p/base/sessionmessages.h b/talk/p2p/base/sessionmessages.h
index 214fa8c..1486f0c 100644
--- a/talk/p2p/base/sessionmessages.h
+++ b/talk/p2p/base/sessionmessages.h
@@ -62,7 +62,7 @@
   ACTION_TRANSPORT_INFO,
   ACTION_TRANSPORT_ACCEPT,
 
-  ACTION_UPDATE,
+  ACTION_DESCRIPTION_INFO,
 };
 
 // Abstraction of a <jingle> element within an <iq> stanza, per XMPP
diff --git a/talk/p2p/base/stun.cc b/talk/p2p/base/stun.cc
index fab14af..b7f6b52 100644
--- a/talk/p2p/base/stun.cc
+++ b/talk/p2p/base/stun.cc
@@ -42,16 +42,17 @@
 const char STUN_ERROR_REASON_SERVER_ERROR[] = "SERVER ERROR";
 
 const char TURN_MAGIC_COOKIE_VALUE[] = { '\x72', '\xC6', '\x4B', '\xC6' };
+const char EMPTY_TRANSACTION_ID[] = "0000000000000000";
 
 StunMessage::StunMessage()
     : type_(0), length_(0),
-      transaction_id_("000000000000") {
+      transaction_id_(EMPTY_TRANSACTION_ID) {
   ASSERT(IsValidTransactionId(transaction_id_));
   attrs_ = new std::vector<StunAttribute*>();
 }
 
 StunMessage::~StunMessage() {
-  for (unsigned i = 0; i < attrs_->size(); i++)
+  for (size_t i = 0; i < attrs_->size(); i++)
     delete (*attrs_)[i];
   delete attrs_;
 }
@@ -63,14 +64,22 @@
   return false;
 }
 
-void StunMessage::SetTransactionID(const std::string& str) {
-  ASSERT(IsValidTransactionId(str));
+bool StunMessage::SetTransactionID(const std::string& str) {
+  if (!IsValidTransactionId(str)) {
+    return false;
+  }
   transaction_id_ = str;
+  return true;
 }
 
 void StunMessage::AddAttribute(StunAttribute* attr) {
   attrs_->push_back(attr);
-  length_ += attr->length() + 4;
+  attr->SetOwner(this);
+  size_t attr_length = attr->length();
+  if (attr_length % 4 != 0) {
+    attr_length += (4 - (attr_length % 4));
+  }
+  length_ += attr_length + 4;
 }
 
 const StunAddressAttribute*
@@ -138,7 +147,7 @@
 }
 
 const StunAttribute* StunMessage::GetAttribute(StunAttributeType type) const {
-  for (unsigned i = 0; i < attrs_->size(); i++) {
+  for (size_t i = 0; i < attrs_->size(); i++) {
     if ((*attrs_)[i]->type() == type)
       return (*attrs_)[i];
   }
@@ -176,7 +185,7 @@
   ASSERT(IsValidTransactionId(transaction_id));
   transaction_id_ = transaction_id;
 
-  if (length_ > buf->Length())
+  if (length_ != buf->Length())
     return false;
 
   attrs_->resize(0);
@@ -189,9 +198,13 @@
     if (!buf->ReadUInt16(&attr_length))
       return false;
 
-    StunAttribute* attr = StunAttribute::Create(attr_type, attr_length);
+    StunAttribute* attr = StunAttribute::Create(attr_type, attr_length,
+                                                this);
     if (!attr) {
       // Skip an unknown attribute.
+      if ((attr_length % 4) != 0) {
+        attr_length += (4 - (attr_length % 4));
+      }
       if (!buf->Consume(attr_length))
         return false;
     } else {
@@ -213,7 +226,7 @@
     buf->WriteUInt32(kStunMagicCookie);
   buf->WriteString(transaction_id_);
 
-  for (unsigned i = 0; i < attrs_->size(); i++) {
+  for (size_t i = 0; i < attrs_->size(); i++) {
     buf->WriteUInt16((*attrs_)[i]->type());
     buf->WriteUInt16((*attrs_)[i]->length());
     (*attrs_)[i]->Write(buf);
@@ -229,17 +242,19 @@
     : type_(type), length_(length) {
 }
 
-StunAttribute* StunAttribute::Create(uint16 type, uint16 length) {
+StunAttribute* StunAttribute::Create(uint16 type,
+                                     uint16 length,
+                                     StunMessage* owner) {
   switch (type) {
     case STUN_ATTR_MAPPED_ADDRESS:
     case STUN_ATTR_DESTINATION_ADDRESS:
     case STUN_ATTR_SOURCE_ADDRESS2:
-      // TODO: Addresses may be different size for IPv6
-      // addresses, but we don't support IPv6 yet. Fix address parsing
-      // when IPv6 support is implemented.
-      if (length != StunAddressAttribute::SIZE)
+      if (length != StunAddressAttribute::SIZE_IP4 &&
+          length != StunAddressAttribute::SIZE_IP6) {
+        LOG(LS_WARNING) << "Invalid length specified for address attribute";
         return NULL;
-      return new StunAddressAttribute(type);
+      }
+      return new StunAddressAttribute(type, length);
 
     case STUN_ATTR_LIFETIME:
     case STUN_ATTR_BANDWIDTH:
@@ -250,14 +265,12 @@
 
     case STUN_ATTR_USERNAME:
     case STUN_ATTR_MAGIC_COOKIE:
-      return (length % 4 == 0) ? new StunByteStringAttribute(type, length) : 0;
+    case STUN_ATTR_DATA:
+      return new StunByteStringAttribute(type, length);
 
     case STUN_ATTR_MESSAGE_INTEGRITY:
       return (length == 20) ? new StunByteStringAttribute(type, length) : 0;
 
-    case STUN_ATTR_DATA:
-      return new StunByteStringAttribute(type, length);
-
     case STUN_ATTR_ERROR_CODE:
       if (length < StunErrorCodeAttribute::MIN_SIZE)
         return NULL;
@@ -267,27 +280,42 @@
       return (length % 2 == 0) ? new StunUInt16ListAttribute(type, length) : 0;
 
     case STUN_ATTR_XOR_MAPPED_ADDRESS:
-      // TODO: Addresses may be different size for IPv6
-      // addresses, but we don't support IPv6 yet. Fix address parsing
-      // when IPv6 support is implemented.
-      if (length != StunAddressAttribute::SIZE)
+      if (length != StunAddressAttribute::SIZE_IP4 &&
+          length != StunAddressAttribute::SIZE_IP6) {
+        LOG(LS_WARNING) << "Invalid length specified for XOR address attribute";
         return NULL;
-      return new StunXorAddressAttribute(type);
+      }
+      return new StunXorAddressAttribute(type, length, owner);
 
     default:
       return NULL;
   }
 }
 
+void StunAttribute::ConsumePadding(talk_base::ByteBuffer* buf) const {
+  int remainder = length_ % 4;
+  if (remainder > 0) {
+    buf->Consume(4 - remainder);
+  }
+}
+
+void StunAttribute::WritePadding(talk_base::ByteBuffer* buf) const {
+  int remainder = length_ % 4;
+  if (remainder > 0) {
+    char zeroes[4] = {0};
+    buf->WriteBytes(zeroes, 4 - remainder);
+  }
+}
+
 StunAddressAttribute* StunAttribute::CreateAddress(uint16 type) {
   switch (type) {
     case STUN_ATTR_MAPPED_ADDRESS:
     case STUN_ATTR_DESTINATION_ADDRESS:
     case STUN_ATTR_SOURCE_ADDRESS2:
-      return new StunAddressAttribute(type);
+      return new StunAddressAttribute(type, StunAddressAttribute::SIZE_IP4);
 
     case STUN_ATTR_XOR_MAPPED_ADDRESS:
-      return new StunXorAddressAttribute(type);
+      return new StunXorAddressAttribute(type, StunAddressAttribute::SIZE_IP4);
 
   default:
     ASSERT(false);
@@ -331,68 +359,146 @@
   return new StunUInt16ListAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES, 0);
 }
 
-StunAddressAttribute::StunAddressAttribute(uint16 type)
-    : StunAttribute(type, SIZE), family_(STUN_ADDRESS_IPV4), port_(0), ip_(0) {
-}
-
-void StunAddressAttribute::SetFamily(StunAddressFamily family) {
-  family_ = family;
-}
+StunAddressAttribute::StunAddressAttribute(uint16 type, uint16 length)
+    : StunAttribute(type, length) { }
 
 bool StunAddressAttribute::Read(ByteBuffer* buf) {
   uint8 dummy;
   if (!buf->ReadUInt8(&dummy))
     return false;
 
-  uint8 family;
-  // We don't expect IPv6 address here because IPv6 addresses would
-  // not pass the attribute size check in StunAttribute::Create().
-  if (!buf->ReadUInt8(&family) || family != STUN_ADDRESS_IPV4) {
+  uint8 stun_family;
+  if (!buf->ReadUInt8(&stun_family)) {
     return false;
   }
-  family_ = static_cast<StunAddressFamily>(family);
-
-  if (!buf->ReadUInt16(&port_))
+  uint16 port;
+  if (!buf->ReadUInt16(&port))
     return false;
-
-  if (!buf->ReadUInt32(&ip_))
+  if (stun_family == STUN_ADDRESS_IPV4) {
+    in_addr v4addr;
+    if (length() != SIZE_IP4) {
+      return false;
+    }
+    if (!buf->ReadBytes(reinterpret_cast<char*>(&v4addr), sizeof(v4addr))) {
+      return false;
+    }
+    talk_base::IPAddress ipaddr(v4addr);
+    SetAddress(talk_base::SocketAddress(ipaddr, port));
+  } else if (stun_family == STUN_ADDRESS_IPV6) {
+    in6_addr v6addr;
+    if (length() != SIZE_IP6) {
+      return false;
+    }
+    if (!buf->ReadBytes(reinterpret_cast<char*>(&v6addr), sizeof(v6addr))) {
+      return false;
+    }
+    talk_base::IPAddress ipaddr(v6addr);
+    SetAddress(talk_base::SocketAddress(ipaddr, port));
+  } else {
     return false;
-
+  }
   return true;
 }
 
 void StunAddressAttribute::Write(ByteBuffer* buf) const {
-  // Only IPv4 address family is currently supported.
-  ASSERT(family_ == STUN_ADDRESS_IPV4);
-
+  StunAddressFamily address_family = family();
+  if (address_family == STUN_ADDRESS_UNDEF) {
+    LOG(LS_ERROR) << "Error writing address attribute: unknown family.";
+    return;
+  }
   buf->WriteUInt8(0);
-  buf->WriteUInt8(family_);
-  buf->WriteUInt16(port_);
-  buf->WriteUInt32(ip_);
+  buf->WriteUInt8(address_family);
+  buf->WriteUInt16(address_.port());
+  switch (address_family) {
+    case STUN_ADDRESS_IPV4: {
+      in_addr v4addr = address_.ipaddr().ipv4_address();
+      buf->WriteBytes(reinterpret_cast<char*>(&v4addr), sizeof(v4addr));
+      break;
+    }
+    case STUN_ADDRESS_IPV6: {
+      in6_addr v6addr = address_.ipaddr().ipv6_address();
+      buf->WriteBytes(reinterpret_cast<char*>(&v6addr), sizeof(v6addr));
+      break;
+    }
+  }
 }
 
-StunXorAddressAttribute::StunXorAddressAttribute(uint16 type)
-    : StunAddressAttribute(type) {
+StunXorAddressAttribute::StunXorAddressAttribute(uint16 type, uint16 length)
+    : StunAddressAttribute(type, length), owner_(NULL) { }
+
+StunXorAddressAttribute::StunXorAddressAttribute(uint16 type,
+                                                 uint16 length,
+                                                 StunMessage* owner)
+    : StunAddressAttribute(type, length), owner_(owner) { }
+
+talk_base::IPAddress StunXorAddressAttribute::GetXoredIP() const {
+  if (owner_) {
+    talk_base::IPAddress ip = ipaddr();
+    switch (ip.family()) {
+      case AF_INET: {
+        in_addr v4addr = ip.ipv4_address();
+        v4addr.s_addr =
+            (v4addr.s_addr ^ talk_base::HostToNetwork32(kStunMagicCookie));
+        return talk_base::IPAddress(v4addr);
+        break;
+      }
+      case AF_INET6: {
+        in6_addr v6addr = ip.ipv6_address();
+        const std::string& transaction_id = owner_->transaction_id();
+        if (transaction_id.length() == 12) {
+          uint32 transactionid_as_ints[3];
+          memcpy(&transactionid_as_ints[0], transaction_id.c_str(),
+                 transaction_id.length());
+          uint32* ip_as_ints = reinterpret_cast<uint32*>(&v6addr.s6_addr);
+          // Transaction ID is in network byte order, but magic cookie
+          // is stored in host byte order.
+          ip_as_ints[0] =
+              (ip_as_ints[0] ^ talk_base::HostToNetwork32(kStunMagicCookie));
+          ip_as_ints[1] = (ip_as_ints[1] ^ transactionid_as_ints[0]);
+          ip_as_ints[2] = (ip_as_ints[2] ^ transactionid_as_ints[1]);
+          ip_as_ints[3] = (ip_as_ints[3] ^ transactionid_as_ints[2]);
+          return talk_base::IPAddress(v6addr);
+        }
+        break;
+      }
+    }
+  }
+  // Invalid ip family or transaction ID, or missing owner.
+  // Return an AF_UNSPEC address.
+  return talk_base::IPAddress();
 }
 
 bool StunXorAddressAttribute::Read(ByteBuffer* buf) {
   if (!StunAddressAttribute::Read(buf))
     return false;
-
-  SetPort(port() ^ (kStunMagicCookie >> 16));
-  SetIP(ip() ^ kStunMagicCookie);
-
+  uint16 xoredport = port() ^ (kStunMagicCookie >> 16);
+  talk_base::IPAddress xored_ip = GetXoredIP();
+  SetAddress(talk_base::SocketAddress(xored_ip, xoredport));
   return true;
 }
 
 void StunXorAddressAttribute::Write(ByteBuffer* buf) const {
-  // Only IPv4 address family is currently supported.
-  ASSERT(family() == STUN_ADDRESS_IPV4);
-
+  StunAddressFamily address_family = family();
+  if (address_family == STUN_ADDRESS_UNDEF) {
+    LOG(LS_ERROR) << "Error writing xor-address attribute: unknown family.";
+    return;
+  }
   buf->WriteUInt8(0);
   buf->WriteUInt8(family());
   buf->WriteUInt16(port() ^ (kStunMagicCookie >> 16));
-  buf->WriteUInt32(ip() ^ kStunMagicCookie);
+  talk_base::IPAddress xored_ip = GetXoredIP();
+  switch (xored_ip.family()) {
+    case AF_INET: {
+      in_addr v4addr = xored_ip.ipv4_address();
+      buf->WriteBytes(reinterpret_cast<const char*>(&v4addr), sizeof(v4addr));
+      break;
+    }
+    case AF_INET6: {
+      in6_addr v6addr = xored_ip.ipv6_address();
+      buf->WriteBytes(reinterpret_cast<const char*>(&v6addr), sizeof(v6addr));
+      break;
+    }
+  }
 }
 
 StunUInt32Attribute::StunUInt32Attribute(uint16 type)
@@ -458,13 +564,18 @@
 
 bool StunByteStringAttribute::Read(ByteBuffer* buf) {
   bytes_ = new char[length()];
-  if (!buf->ReadBytes(bytes_, length()))
+  if (!buf->ReadBytes(bytes_, length())) {
     return false;
+  }
+
+  ConsumePadding(buf);
+
   return true;
 }
 
 void StunByteStringAttribute::Write(ByteBuffer* buf) const {
   buf->WriteBytes(bytes_, length());
+  WritePadding(buf);
 }
 
 StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, uint16 length)
@@ -496,6 +607,7 @@
 
   if (!buf->ReadString(&reason_, length() - 4))
     return false;
+  ConsumePadding(buf);
 
   return true;
 }
@@ -503,6 +615,7 @@
 void StunErrorCodeAttribute::Write(ByteBuffer* buf) const {
   buf->WriteUInt32(error_code());
   buf->WriteString(reason_);
+  WritePadding(buf);
 }
 
 StunUInt16ListAttribute::StunUInt16ListAttribute(uint16 type, uint16 length)
@@ -538,12 +651,20 @@
       return false;
     attr_types_->push_back(attr);
   }
+  // Padding of these attributes is done in RFC 5389 style. This is
+  // slightly different from RFC3489, but it shouldn't be important.
+  // RFC3489 pads out to a 32 bit boundary by duplicating one of the
+  // entries in the list (not necessarily the last one - it's unspecified).
+  // RFC5389 pads on the end, and the bytes are always ignored.
+  ConsumePadding(buf);
   return true;
 }
 
 void StunUInt16ListAttribute::Write(ByteBuffer* buf) const {
-  for (unsigned i = 0; i < attr_types_->size(); i++)
+  for (size_t i = 0; i < attr_types_->size(); i++) {
     buf->WriteUInt16((*attr_types_)[i]);
+  }
+  WritePadding(buf);
 }
 
 StunMessageType GetStunResponseType(StunMessageType request_type) {
@@ -572,4 +693,4 @@
   }
 }
 
-} // namespace cricket
+}  // namespace cricket
diff --git a/talk/p2p/base/stun.h b/talk/p2p/base/stun.h
index efc5f2c..b8b470f 100644
--- a/talk/p2p/base/stun.h
+++ b/talk/p2p/base/stun.h
@@ -36,6 +36,7 @@
 
 #include "talk/base/basictypes.h"
 #include "talk/base/bytebuffer.h"
+#include "talk/base/socketaddress.h"
 
 namespace cricket {
 
@@ -63,19 +64,19 @@
 // implemented yet, particularly REALM, NONCE, SOFTWARE,
 // ALTERNATE-SERVE and FINGEPRINT. Implement them.
 enum StunAttributeType {
-  STUN_ATTR_MAPPED_ADDRESS        = 0x0001, // Address
-  STUN_ATTR_USERNAME              = 0x0006, // ByteString, multiple of 4 bytes
-  STUN_ATTR_MESSAGE_INTEGRITY     = 0x0008, // ByteString, 20 bytes
-  STUN_ATTR_ERROR_CODE            = 0x0009, // ErrorCode
-  STUN_ATTR_UNKNOWN_ATTRIBUTES    = 0x000a, // UInt16List
-  STUN_ATTR_LIFETIME              = 0x000d, // UInt32
-  STUN_ATTR_MAGIC_COOKIE          = 0x000f, // ByteString, 4 bytes
-  STUN_ATTR_BANDWIDTH             = 0x0010, // UInt32
-  STUN_ATTR_DESTINATION_ADDRESS   = 0x0011, // Address
-  STUN_ATTR_SOURCE_ADDRESS2       = 0x0012, // Address
-  STUN_ATTR_DATA                  = 0x0013, // ByteString
-  STUN_ATTR_XOR_MAPPED_ADDRESS    = 0x0020, // XorAddress
-  STUN_ATTR_OPTIONS               = 0x8001  // UInt32
+  STUN_ATTR_MAPPED_ADDRESS        = 0x0001,  // Address
+  STUN_ATTR_USERNAME              = 0x0006,  // ByteString, multiple of 4 bytes
+  STUN_ATTR_MESSAGE_INTEGRITY     = 0x0008,  // ByteString, 20 bytes
+  STUN_ATTR_ERROR_CODE            = 0x0009,  // ErrorCode
+  STUN_ATTR_UNKNOWN_ATTRIBUTES    = 0x000a,  // UInt16List
+  STUN_ATTR_LIFETIME              = 0x000d,  // UInt32
+  STUN_ATTR_MAGIC_COOKIE          = 0x000f,  // ByteString, 4 bytes
+  STUN_ATTR_BANDWIDTH             = 0x0010,  // UInt32
+  STUN_ATTR_DESTINATION_ADDRESS   = 0x0011,  // Address
+  STUN_ATTR_SOURCE_ADDRESS2       = 0x0012,  // Address
+  STUN_ATTR_DATA                  = 0x0013,  // ByteString
+  STUN_ATTR_XOR_MAPPED_ADDRESS    = 0x0020,  // XorAddress
+  STUN_ATTR_OPTIONS               = 0x8001   // UInt32
 };
 
 enum StunErrorCodes {
@@ -91,6 +92,8 @@
 };
 
 enum StunAddressFamily {
+  // NB: UNDEF is not part of the STUN spec.
+  STUN_ADDRESS_UNDEF = 0,
   STUN_ADDRESS_IPV4 = 1,
   STUN_ADDRESS_IPV6 = 2
 };
@@ -122,7 +125,7 @@
 // appropriate class (see above).  The Get* methods will return instances of
 // that attribute class.
 class StunMessage {
-public:
+ public:
   StunMessage();
   ~StunMessage();
   StunMessageType type() const { return static_cast<StunMessageType>(type_); }
@@ -137,7 +140,7 @@
   bool IsLegacy() const;
 
   void SetType(StunMessageType type) { type_ = type; }
-  void SetTransactionID(const std::string& str);
+  bool SetTransactionID(const std::string& str);
 
   const StunAddressAttribute* GetAddress(StunAttributeType type) const;
   const StunUInt32Attribute* GetUInt32(StunAttributeType type) const;
@@ -156,7 +159,7 @@
   // successful.
   void Write(talk_base::ByteBuffer* buf) const;
 
-private:
+ private:
   uint16 type_;
   uint16 length_;
   std::string transaction_id_;
@@ -168,7 +171,7 @@
 
 // Base class for all STUN/TURN attributes.
 class StunAttribute {
-public:
+ public:
   virtual ~StunAttribute() {}
 
   StunAttributeType type() const {
@@ -176,6 +179,9 @@
   }
   uint16 length() const { return length_; }
 
+  // Only XorAddressAttribute needs this so far.
+  virtual void SetOwner(StunMessage* owner) { }
+
   // Reads the body (not the type or length) for this type of attribute from
   // the given buffer.  Return value is true if successful.
   virtual bool Read(talk_base::ByteBuffer* buf) = 0;
@@ -184,8 +190,9 @@
   // value is true if successful.
   virtual void Write(talk_base::ByteBuffer* buf) const = 0;
 
-  // Creates an attribute object with the given type and len.
-  static StunAttribute* Create(uint16 type, uint16 length);
+  // Creates an attribute object with the given type, length and transaction id.
+  static StunAttribute* Create(uint16 type, uint16 length,
+                               StunMessage* owner);
 
   // Creates an attribute object with the given type and smallest length.
   static StunAddressAttribute* CreateAddress(uint16 type);
@@ -195,52 +202,94 @@
   static StunUInt16ListAttribute* CreateUnknownAttributes();
   static StunTransportPrefsAttribute* CreateTransportPrefs();
 
-protected:
+ protected:
   StunAttribute(uint16 type, uint16 length);
-
   void SetLength(uint16 length) { length_ = length; }
+  void WritePadding(talk_base::ByteBuffer* buf) const;
+  void ConsumePadding(talk_base::ByteBuffer* buf) const;
 
-private:
+ private:
   uint16 type_;
   uint16 length_;
 };
 
 // Implements STUN/TURN attributes that record an Internet address.
 class StunAddressAttribute : public StunAttribute {
-public:
-  explicit StunAddressAttribute(uint16 type);
+ public:
+  StunAddressAttribute(uint16 type, uint16 length);
 
-  static const uint16 SIZE = 8;
+  static const uint16 SIZE_UNDEF = 0;
+  static const uint16 SIZE_IP4 = 8;
+  static const uint16 SIZE_IP6 = 20;
 
-  StunAddressFamily family() const { return family_; }
-  uint16 port() const { return port_; }
-  uint32 ip() const { return ip_; }
+  StunAddressFamily family() const {
+    switch (address_.ipaddr().family()) {
+      case AF_INET:
+        return STUN_ADDRESS_IPV4;
+      case AF_INET6:
+        return STUN_ADDRESS_IPV6;
+    }
+    return STUN_ADDRESS_UNDEF;
+  }
 
-  void SetFamily(StunAddressFamily family);
-  void SetIP(uint32 ip) { ip_ = ip; }
-  void SetPort(uint16 port) { port_ = port; }
+  uint16 port() const { return address_.port(); }
+  const talk_base::IPAddress& ipaddr() const { return address_.ipaddr(); }
+  void SetAddress(const talk_base::SocketAddress& addr) {
+    address_ = addr;
+    EnsureAddressLength();
+  }
+  const talk_base::SocketAddress& GetAddress() const { return address_; }
+  void SetIP(const talk_base::IPAddress& ip) {
+    address_.SetIP(ip);
+    EnsureAddressLength();
+  }
+  void SetPort(uint16 port) { address_.SetPort(port); }
 
   virtual bool Read(talk_base::ByteBuffer* buf);
   virtual void Write(talk_base::ByteBuffer* buf) const;
 
-private:
-  StunAddressFamily family_;
-  uint16 port_;
-  uint32 ip_;
+ private:
+  void EnsureAddressLength() {
+    switch (family()) {
+      case STUN_ADDRESS_IPV4: {
+        SetLength(SIZE_IP4);
+        break;
+      }
+      case STUN_ADDRESS_IPV6: {
+        SetLength(SIZE_IP6);
+        break;
+      }
+      default: {
+        SetLength(SIZE_UNDEF);
+        break;
+      }
+    }
+  }
+  talk_base::SocketAddress address_;
 };
 
-// Implements STUN/TURN attributes that record an Internet address.
+// Implements STUN/TURN attributes that record an Internet address. When encoded
+// in a STUN message, the address contained in this attribute is XORed with the
+// transaction ID of the message.
 class StunXorAddressAttribute : public StunAddressAttribute {
-public:
-  explicit StunXorAddressAttribute(uint16 type);
+ public:
+  StunXorAddressAttribute(uint16 type, uint16 length);
+  StunXorAddressAttribute(uint16 type, uint16 length,
+                          StunMessage* owner);
 
+  virtual void SetOwner(StunMessage* owner) {
+    owner_ = owner;
+  }
   virtual bool Read(talk_base::ByteBuffer* buf);
   virtual void Write(talk_base::ByteBuffer* buf) const;
+ private:
+  talk_base::IPAddress GetXoredIP() const;
+  StunMessage* owner_;
 };
 
 // Implements STUN/TURN attributs that record a 32-bit integer.
 class StunUInt32Attribute : public StunAttribute {
-public:
+ public:
   explicit StunUInt32Attribute(uint16 type);
 
   static const uint16 SIZE = 4;
@@ -255,13 +304,13 @@
   bool Read(talk_base::ByteBuffer* buf);
   void Write(talk_base::ByteBuffer* buf) const;
 
-private:
+ private:
   uint32 bits_;
 };
 
-// Implements STUN/TURN attributs that record an arbitrary byte string
+// Implements STUN/TURN attributes that record an arbitrary byte string
 class StunByteStringAttribute : public StunAttribute {
-public:
+ public:
   StunByteStringAttribute(uint16 type, uint16 length);
   ~StunByteStringAttribute();
 
@@ -269,7 +318,7 @@
 
   void SetBytes(char* bytes, uint16 length);
 
-  void CopyBytes(const char* bytes); // uses strlen
+  void CopyBytes(const char* bytes);  // uses strlen
   void CopyBytes(const void* bytes, uint16 length);
 
   uint8 GetByte(int index) const;
@@ -278,13 +327,13 @@
   bool Read(talk_base::ByteBuffer* buf);
   void Write(talk_base::ByteBuffer* buf) const;
 
-private:
+ private:
   char* bytes_;
 };
 
-// Implements STUN/TURN attributs that record an error code.
+// Implements STUN/TURN attributes that record an error code.
 class StunErrorCodeAttribute : public StunAttribute {
-public:
+ public:
   StunErrorCodeAttribute(uint16 type, uint16 length);
   ~StunErrorCodeAttribute();
 
@@ -303,15 +352,15 @@
   bool Read(talk_base::ByteBuffer* buf);
   void Write(talk_base::ByteBuffer* buf) const;
 
-private:
+ private:
   uint8 class_;
   uint8 number_;
   std::string reason_;
 };
 
-// Implements STUN/TURN attributs that record a list of attribute names.
+// Implements STUN/TURN attributes that record a list of attribute names.
 class StunUInt16ListAttribute : public StunAttribute {
-public:
+ public:
   StunUInt16ListAttribute(uint16 type, uint16 length);
   ~StunUInt16ListAttribute();
 
@@ -323,7 +372,7 @@
   bool Read(talk_base::ByteBuffer* buf);
   void Write(talk_base::ByteBuffer* buf) const;
 
-private:
+ private:
   std::vector<uint16>* attr_types_;
 };
 
@@ -339,6 +388,6 @@
 // Returns the error response type for the given request type.
 StunMessageType GetStunErrorResponseType(StunMessageType request_type);
 
-} // namespace cricket
+}  // namespace cricket
 
 #endif  // TALK_P2P_BASE_STUN_H_
diff --git a/talk/p2p/base/stun_unittest.cc b/talk/p2p/base/stun_unittest.cc
new file mode 100644
index 0000000..1152bee
--- /dev/null
+++ b/talk/p2p/base/stun_unittest.cc
@@ -0,0 +1,997 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+class StunTest : public ::testing::Test {
+ protected:
+  void CheckStunHeader(const StunMessage& msg, StunMessageType expected_type,
+                       size_t expected_length) {
+    ASSERT_EQ(expected_type, msg.type());
+    ASSERT_EQ(expected_length, msg.length());
+  }
+
+  void CheckStunTransactionID(const StunMessage& msg,
+                              const unsigned char* expectedID, size_t length) {
+    ASSERT_EQ(0, std::memcmp(msg.transaction_id().c_str(),
+                             expectedID, length));
+  }
+
+  void CheckStunAddressAttribute(const StunAddressAttribute* addr,
+                                 StunAddressFamily expected_family,
+                                 int expected_port,
+                                 talk_base::IPAddress expected_address) {
+    ASSERT_EQ(expected_family, addr->family());
+    ASSERT_EQ(expected_port, addr->port());
+
+    if (addr->family() == STUN_ADDRESS_IPV4) {
+      in_addr v4_address = expected_address.ipv4_address();
+      in_addr stun_address = addr->ipaddr().ipv4_address();
+      ASSERT_EQ(0, std::memcmp(&v4_address, &stun_address,
+                               sizeof(stun_address)));
+    } else if (addr->family() == STUN_ADDRESS_IPV6) {
+      in6_addr v6_address = expected_address.ipv6_address();
+      in6_addr stun_address = addr->ipaddr().ipv6_address();
+      ASSERT_EQ(0, std::memcmp(&v6_address, &stun_address,
+                               sizeof(stun_address)));
+    } else {
+      ASSERT_TRUE(addr->family() == STUN_ADDRESS_IPV6 ||
+                  addr->family() == STUN_ADDRESS_IPV4);
+    }
+  }
+
+  size_t ReadStunMessageTestCase(StunMessage* msg,
+                                 const unsigned char* testcase,
+                                 size_t size) {
+    const char* input = reinterpret_cast<const char*>(testcase);
+    talk_base::ByteBuffer buf(input, size);
+    if (msg->Read(&buf)) {
+      // Returns the size the stun message should report itself as being
+      return (size - 20);
+    } else {
+      return 0;
+    }
+  }
+};
+
+
+// Sample STUN packets with various attributes
+// Gathered by wiresharking pjproject's pjnath test programs
+// pjproject available at www.pjsip.org
+
+static const unsigned char kStunMessageWithIPv6MappedAddress[] = {
+  0x00, 0x01, 0x00, 0x18,  // message header
+  0x21, 0x12, 0xa4, 0x42,  // transaction id
+  0x29, 0x1f, 0xcd, 0x7c,
+  0xba, 0x58, 0xab, 0xd7,
+  0xf2, 0x41, 0x01, 0x00,
+  0x00, 0x01, 0x00, 0x14,  // Address type (mapped), length
+  0x00, 0x02, 0xb8, 0x81,  // family (IPv6), port
+  0x24, 0x01, 0xfa, 0x00,  // an IPv6 address
+  0x00, 0x04, 0x10, 0x00,
+  0xbe, 0x30, 0x5b, 0xff,
+  0xfe, 0xe5, 0x00, 0xc3
+};
+
+static const unsigned char kStunMessageWithIPv4MappedAddress[] = {
+  0x01, 0x01, 0x00, 0x0c,   // binding response, length 12
+  0x21, 0x12, 0xa4, 0x42,   // magic cookie
+  0x29, 0x1f, 0xcd, 0x7c,   // transaction ID
+  0xba, 0x58, 0xab, 0xd7,
+  0xf2, 0x41, 0x01, 0x00,
+  0x00, 0x01, 0x00, 0x08,  // Mapped, 8 byte length
+  0x00, 0x01, 0x9d, 0xfc,  // AF_INET, unxor-ed port
+  0xac, 0x17, 0x44, 0xe6   // IPv4 address
+};
+
+// Test XOR-mapped IP addresses:
+static const unsigned char kStunMessageWithIPv6XorMappedAddress[] = {
+  0x01, 0x01, 0x00, 0x18,  // message header (binding response)
+  0x21, 0x12, 0xa4, 0x42,  // magic cookie (rfc5389)
+  0xe3, 0xa9, 0x46, 0xe1,  // transaction ID
+  0x7c, 0x00, 0xc2, 0x62,
+  0x54, 0x08, 0x01, 0x00,
+  0x00, 0x20, 0x00, 0x14,  // Address Type (XOR), length
+  0x00, 0x02, 0xcb, 0x5b,  // family, XOR-ed port
+  0x05, 0x13, 0x5e, 0x42,  // XOR-ed IPv6 address
+  0xe3, 0xad, 0x56, 0xe1,
+  0xc2, 0x30, 0x99, 0x9d,
+  0xaa, 0xed, 0x01, 0xc3
+};
+
+static const unsigned char kStunMessageWithIPv4XorMappedAddress[] = {
+  0x01, 0x01, 0x00, 0x0c,  // message header (binding response)
+  0x21, 0x12, 0xa4, 0x42,  // magic cookie
+  0x29, 0x1f, 0xcd, 0x7c,  // transaction ID
+  0xba, 0x58, 0xab, 0xd7,
+  0xf2, 0x41, 0x01, 0x00,
+  0x00, 0x20, 0x00, 0x08,  // address type (xor), length
+  0x00, 0x01, 0xfc, 0xb5,  // family (AF_INET), XOR-ed port
+  0x8d, 0x05, 0xe0, 0xa4   // IPv4 address
+};
+
+// ByteString Attribute (username)
+static const unsigned char kStunMessageWithByteStringAttribute[] = {
+  0x00, 0x01, 0x00, 0x0c,
+  0x21, 0x12, 0xa4, 0x42,
+  0xe3, 0xa9, 0x46, 0xe1,
+  0x7c, 0x00, 0xc2, 0x62,
+  0x54, 0x08, 0x01, 0x00,
+  0x00, 0x06, 0x00, 0x08,  // username attribute (length 8)
+  0x61, 0x62, 0x63, 0x64,  // abcdefgh
+  0x65, 0x66, 0x67, 0x68
+};
+
+// Message with an unknown but comprehensible optional attribute.
+// Parsing should succeed despite this unknown attribute.
+static const unsigned char kStunMessageWithUnknownAttribute[] = {
+  0x00, 0x01, 0x00, 0x14,
+  0x21, 0x12, 0xa4, 0x42,
+  0xe3, 0xa9, 0x46, 0xe1,
+  0x7c, 0x00, 0xc2, 0x62,
+  0x54, 0x08, 0x01, 0x00,
+  0x00, 0xaa, 0x00, 0x07,  // Unknown attribute, length 7 (needs padding!)
+  0x61, 0x62, 0x63, 0x64,  // abcdefg + padding
+  0x65, 0x66, 0x67, 0x00,
+  0x00, 0x0d, 0x00, 0x04,  // Followed by a known attribute we can
+  0x00, 0x00, 0x00, 0x0b   // check for.
+};
+
+// ByteString Attribute (username) with padding byte
+static const unsigned char kStunMessageWithPaddedByteStringAttribute[] = {
+  0x00, 0x01, 0x00, 0x08,
+  0x21, 0x12, 0xa4, 0x42,
+  0xe3, 0xa9, 0x46, 0xe1,
+  0x7c, 0x00, 0xc2, 0x62,
+  0x54, 0x08, 0x01, 0x00,
+  0x00, 0x06, 0x00, 0x03,  // username attribute (length 3)
+  0x61, 0x62, 0x63, 0xcc   // abc
+};
+
+// Message with an Unknown Attributes (uint16 list) attribute.
+static const unsigned char kStunMessageWithUInt16ListAttribute[] = {
+  0x00, 0x01, 0x00, 0x0c,
+  0x21, 0x12, 0xa4, 0x42,
+  0xe3, 0xa9, 0x46, 0xe1,
+  0x7c, 0x00, 0xc2, 0x62,
+  0x54, 0x08, 0x01, 0x00,
+  0x00, 0x0a, 0x00, 0x06,  // username attribute (length 6)
+  0x00, 0x01, 0x10, 0x00,  // three attributes plus padding
+  0xAB, 0xCU, 0xBE, 0xEF
+};
+
+// Error response message (unauthorized)
+static const unsigned char kStunMessageWithErrorAttribute[] = {
+  0x01, 0x13, 0x00, 0x14,
+  0x21, 0x12, 0xa4, 0x42,
+  0x29, 0x1f, 0xcd, 0x7c,
+  0xba, 0x58, 0xab, 0xd7,
+  0xf2, 0x41, 0x01, 0x00,
+  0x00, 0x09, 0x00, 0x10,
+  0x00, 0x00, 0x04, 0x01,
+  0x55, 0x6e, 0x61, 0x75,
+  0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x7a, 0x65, 0x64
+};
+
+// Message with an address attribute with an unknown address family,
+// and a byte string attribute. Check that we quit reading after the
+// bogus address family and don't read the username attribute.
+static const unsigned char kStunMessageWithInvalidAddressFamily[] = {
+  0x01, 0x01, 0x00, 0x18,   // binding response, length 24
+  0x21, 0x12, 0xa4, 0x42,   // magic cookie
+  0x29, 0x1f, 0xcd, 0x7c,   // transaction ID
+  0xba, 0x58, 0xab, 0xd7,
+  0xf2, 0x41, 0x01, 0x00,
+  0x00, 0x01, 0x00, 0x08,  // Mapped address, 4 byte length
+  0x00, 0x09, 0xfe, 0xed,  // Bogus address family (port unimportant).
+  0xac, 0x17, 0x44, 0xe6,  // Should be skipped.
+  0x00, 0x06, 0x00, 0x08,  // Username attribute (length 8)
+  0x61, 0x62, 0x63, 0x64,  // abcdefgh
+  0x65, 0x66, 0x67, 0x68
+};
+
+// Message with an address attribute with an invalid address length.
+// Should fail to be read.
+static const unsigned char kStunMessageWithInvalidAddressLength[] = {
+  0x01, 0x01, 0x00, 0x18,   // binding response, length 24
+  0x21, 0x12, 0xa4, 0x42,   // magic cookie
+  0x29, 0x1f, 0xcd, 0x7c,   // transaction ID
+  0xba, 0x58, 0xab, 0xd7,
+  0xf2, 0x41, 0x01, 0x00,
+  0x00, 0x01, 0x00, 0x0c,  // Mapped address, 12 byte length
+  0x00, 0x01, 0xfe, 0xed,  // Claims to be AF_INET.
+  0xac, 0x17, 0x44, 0xe6,
+  0x00, 0x06, 0x00, 0x08
+};
+
+// Sample messages with an invalid length Field
+
+// The actual length in bytes of the invalid messages (including STUN header)
+static const int kRealLengthOfInvalidLengthTestCases = 32;
+
+static const unsigned char kStunMessageWithZeroLength[] = {
+  0x00, 0x01, 0x00, 0x00,  // length of 0 (last 2 bytes)
+  0x21, 0x12, 0xA4, 0x42,  // magic cookie
+  '0', '1', '2', '3',      // transaction id
+  '4', '5', '6', '7',
+  '8', '9', 'a', 'b',
+  0x00, 0x20, 0x00, 0x08,  // xor mapped address
+  0x00, 0x01, 0x21, 0x1F,
+  0x21, 0x12, 0xA4, 0x53,
+};
+
+static const unsigned char kStunMessageWithExcessLength[] = {
+  0x00, 0x01, 0x00, 0x55,  // length of 85
+  0x21, 0x12, 0xA4, 0x42,  // magic cookie
+  '0', '1', '2', '3',      // transaction id
+  '4', '5', '6', '7',
+  '8', '9', 'a', 'b',
+  0x00, 0x20, 0x00, 0x08,  // xor mapped address
+  0x00, 0x01, 0x21, 0x1F,
+  0x21, 0x12, 0xA4, 0x53,
+};
+
+static const unsigned char kStunMessageWithSmallLength[] = {
+  0x00, 0x01, 0x00, 0x03,  // length of 3
+  0x21, 0x12, 0xA4, 0x42,  // magic cookie
+  '0', '1', '2', '3',      // transaction id
+  '4', '5', '6', '7',
+  '8', '9', 'a', 'b',
+  0x00, 0x20, 0x00, 0x08,  // xor mapped address
+  0x00, 0x01, 0x21, 0x1F,
+  0x21, 0x12, 0xA4, 0x53,
+};
+
+// Legacy STUN tests.
+// Included for completeness, but it's not recommended to change these.
+static const unsigned char kStunMessageWithManyAttributes[] = {
+  0x00, 0x01, 0x00, 136,   // message header
+  0x21, 0x12, 0xA4, 0x42,  // magic cookie
+  '0', '1', '2', '3',      // transaction id
+  '4', '5', '6', '7',
+  '8', '9', 'a', 'b',
+  0x00, 0x01, 0x00, 8,     // mapped address
+  0x00, 0x01, 0x00, 13,
+  0x00, 0x00, 0x00, 17,
+  0x00, 0x06, 0x00, 12,    // username
+  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+  0x00, 0x08, 0x00, 20,    // message integrity
+  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+  'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+  0x00, 0x09, 0x00, 12,    // error code
+  0x00, 0x00, 2, 10,
+  'f', 'o', 'o', ' ', 'b', 'a', 'r', '!',
+  0x00, 0x0a, 0x00, 4,     // unknown attributes
+  0x00, 0x01, 0x00, 0x02,
+  0x00, 0x0d, 0x00, 4,     // lifetime
+  0x00, 0x00, 0x00, 11,
+  0x00, 0x0f, 0x00, 4,     // magic cookie
+  0x72, 0xc6, 0x4b, 0xc6,
+  0x00, 0x10, 0x00, 4,     // bandwidth
+  0x00, 0x00, 0x00, 6,
+  0x00, 0x11, 0x00, 8,     // destination address
+  0x00, 0x01, 0x00, 13,
+  0x00, 0x00, 0x00, 17,
+  0x00, 0x12, 0x00, 8,     // source address 2
+  0x00, 0x01, 0x00, 13,
+  0x00, 0x00, 0x00, 17,
+  0x00, 0x13, 0x00, 7,     // data
+  'a', 'b', 'c', 'd', 'e', 'f', 'g', 0  // DATA must be padded per rfc5766.
+};
+
+// RTCP packet, for testing we correctly ignore non stun packet types.
+// V=2, P=false, RC=0, Type=200, Len=6, Sender-SSRC=85, etc
+static const unsigned char kRtcpPacket[] = {
+  0x80, 0xc8, 0x00, 0x06, 0x00, 0x00, 0x00, 0x55,
+  0xce, 0xa5, 0x18, 0x3a, 0x39, 0xcc, 0x7d, 0x09,
+  0x23, 0xed, 0x19, 0x07, 0x00, 0x00, 0x01, 0x56,
+  0x00, 0x03, 0x73, 0x50,
+};
+
+// A transaction ID without the 'magic cookie' portion
+// pjnat's test programs use this transaction ID a lot.
+const unsigned char kTestTransactionId1[] = { 0x029, 0x01f, 0x0cd, 0x07c,
+                                              0x0ba, 0x058, 0x0ab, 0x0d7,
+                                              0x0f2, 0x041, 0x001, 0x000 };
+
+// They use this one sometimes too.
+const unsigned char kTestTransactionId2[] = { 0x0e3, 0x0a9, 0x046, 0x0e1,
+                                              0x07c, 0x000, 0x0c2, 0x062,
+                                              0x054, 0x008, 0x001, 0x000 };
+
+const in6_addr kIPv6TestAddress1 = { { { 0x24, 0x01, 0xfa, 0x00,
+                                         0x00, 0x04, 0x10, 0x00,
+                                         0xbe, 0x30, 0x5b, 0xff,
+                                         0xfe, 0xe5, 0x00, 0xc3 } } };
+const in6_addr kIPv6TestAddress2 = { { { 0x24, 0x01, 0xfa, 0x00,
+                                         0x00, 0x04, 0x10, 0x12,
+                                         0x06, 0x0c, 0xce, 0xff,
+                                         0xfe, 0x1f, 0x61, 0xa4 } } };
+
+// This is kIPv6TestAddress1 xor-ed with kTestTransactionID2.
+const in6_addr kIPv6XoredTestAddress = { { { 0x05, 0x13, 0x5e, 0x42,
+                                             0xe3, 0xad, 0x56, 0xe1,
+                                             0xc2, 0x30, 0x99, 0x9d,
+                                             0xaa, 0xed, 0x01, 0xc3 } } };
+
+#ifdef POSIX
+const in_addr kIPv4TestAddress1 =  { 0xe64417ac };
+// This is kIPv4TestAddress xored with the STUN magic cookie.
+const in_addr kIPv4XoredTestAddress = { 0x8d05e0a4 };
+#elif defined WIN32
+// Windows in_addr has a union with a uchar[] array first.
+const in_addr kIPv4XoredTestAddress = { { 0x8d, 0x05, 0xe0, 0xa4 } };
+const in_addr kIPv4TestAddress1 =  { { 0x0ac, 0x017, 0x044, 0x0e6 } };
+#endif
+const char kTestUserName1[] = "abcdefgh";
+const char kTestUserName2[] = "abc";
+const char kTestErrorReason[] = "Unauthorized";
+const int kTestErrorClass = 4;
+const int kTestErrorNumber = 1;
+
+const int kTestMessagePort1 = 59977;
+const int kTestMessagePort2 = 47233;
+const int kTestMessagePort3 = 56743;
+const int kTestMessagePort4 = 40444;
+
+#define ReadStunMessage(X, Y) ReadStunMessageTestCase(X, Y, sizeof(Y));
+
+TEST_F(StunTest, ReadMessageWithIPv4AddressAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4MappedAddress);
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+  const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+  talk_base::IPAddress test_address(kIPv4TestAddress1);
+  CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+                            kTestMessagePort4, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithIPv4XorAddressAttribute) {
+  StunMessage msg;
+  StunMessage msg2;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4XorMappedAddress);
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+  const StunAddressAttribute* addr =
+      msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  talk_base::IPAddress test_address(kIPv4TestAddress1);
+  CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+                            kTestMessagePort3, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithIPv6AddressAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6MappedAddress);
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+  talk_base::IPAddress test_address(kIPv6TestAddress1);
+
+  const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+  CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+                            kTestMessagePort2, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithInvalidAddressAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6MappedAddress);
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+  talk_base::IPAddress test_address(kIPv6TestAddress1);
+
+  const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+  CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+                            kTestMessagePort2, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithIPv6XorAddressAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6XorMappedAddress);
+
+  talk_base::IPAddress test_address(kIPv6TestAddress1);
+
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+  CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+
+  const StunAddressAttribute* addr =
+      msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+                            kTestMessagePort1, test_address);
+}
+
+TEST_F(StunTest, SetIPv6XorAddressAttributeOwner) {
+  StunMessage msg;
+  StunMessage msg2;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6XorMappedAddress);
+
+  talk_base::IPAddress test_address(kIPv6TestAddress1);
+
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+  CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+
+  const StunAddressAttribute* addr =
+      msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+                            kTestMessagePort1, test_address);
+
+  // Owner with a different transaction ID.
+  msg2.SetTransactionID("ABCDABCDABCD");
+  StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20);
+  addr2.SetIP(addr->ipaddr());
+  addr2.SetPort(addr->port());
+  addr2.SetOwner(&msg2);
+  // The internal IP address shouldn't change.
+  ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+
+  talk_base::ByteBuffer correct_buf;
+  talk_base::ByteBuffer wrong_buf;
+  addr->Write(&correct_buf);
+  addr2.Write(&wrong_buf);
+  // But when written out, the buffers should look different.
+  ASSERT_NE(0, std::memcmp(correct_buf.Data(),
+                           wrong_buf.Data(),
+                           wrong_buf.Length()));
+  // And when reading a known good value, the address should be wrong
+  addr2.Read(&correct_buf);
+  ASSERT_NE(addr->ipaddr(), addr2.ipaddr());
+  addr2.SetIP(addr->ipaddr());
+  addr2.SetPort(addr->port());
+  // Try writing with no owner at all. Should write 4 bytes (1 byte reserved,
+  // 2 bytes port, 1 byte address family, 0 bytes address). This is an invalid
+  // but well-formed address attribute.
+  addr2.SetOwner(NULL);
+  ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+  wrong_buf.Shift(wrong_buf.Length());
+  addr2.Write(&wrong_buf);
+  ASSERT_EQ(4U, wrong_buf.Length());
+  ASSERT_NE(wrong_buf.Length(), correct_buf.Length());
+  ASSERT_NE(0, std::memcmp(correct_buf.Data(),
+                           wrong_buf.Data(),
+                           wrong_buf.Length()));
+}
+
+TEST_F(StunTest, SetIPv4XorAddressAttributeOwner) {
+  // Unlike the IPv6XorAddressAttributeOwner test, IPv4 XOR address attributes
+  // should _not_ be affected by a change in owner. IPv4 XOR address uses the
+  // magic cookie value which is fixed.
+  StunMessage msg;
+  StunMessage msg2;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4XorMappedAddress);
+
+  talk_base::IPAddress test_address(kIPv4TestAddress1);
+
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+  const StunAddressAttribute* addr =
+      msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+                            kTestMessagePort3, test_address);
+
+  // Owner with a different transaction ID.
+  msg2.SetTransactionID("ABCDABCDABCD");
+  StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20);
+  addr2.SetIP(addr->ipaddr());
+  addr2.SetPort(addr->port());
+  addr2.SetOwner(&msg2);
+  // The internal IP address shouldn't change.
+  ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+
+  talk_base::ByteBuffer correct_buf;
+  talk_base::ByteBuffer wrong_buf;
+  addr->Write(&correct_buf);
+  addr2.Write(&wrong_buf);
+  // The same address data should be written.
+  ASSERT_EQ(0, std::memcmp(correct_buf.Data(),
+                           wrong_buf.Data(),
+                           wrong_buf.Length()));
+  // And an attribute should be able to un-XOR an address belonging to a message
+  // with a different transaction ID.
+  addr2.Read(&correct_buf);
+  ASSERT_EQ(addr->ipaddr(), addr2.ipaddr());
+
+  // However, no owner is still an error. Write 4 bytes and a 0 length address.
+  addr2.SetOwner(NULL);
+  ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+  wrong_buf.Shift(wrong_buf.Length());
+  addr2.Write(&wrong_buf);
+  ASSERT_NE(wrong_buf.Length(), correct_buf.Length());
+  ASSERT_NE(0, std::memcmp(correct_buf.Data(),
+                           wrong_buf.Data(),
+                           wrong_buf.Length()));
+}
+
+TEST_F(StunTest, CreateIPv6AddressAttribute) {
+  talk_base::IPAddress test_ip(kIPv6TestAddress2);
+
+  StunAddressAttribute* addr =
+      StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+  talk_base::SocketAddress test_addr(test_ip, kTestMessagePort2);
+  addr->SetAddress(test_addr);
+
+  CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+                            kTestMessagePort2, test_ip);
+  delete addr;
+}
+
+TEST_F(StunTest, CreateIPv4AddressAttribute) {
+  struct in_addr test_in_addr;
+  test_in_addr.s_addr = 0xBEB0B0BE;
+  talk_base::IPAddress test_ip(test_in_addr);
+
+  StunAddressAttribute* addr =
+      StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+  talk_base::SocketAddress test_addr(test_ip, kTestMessagePort2);
+  addr->SetAddress(test_addr);
+
+  CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+                            kTestMessagePort2, test_ip);
+  delete addr;
+}
+
+TEST_F(StunTest, WriteMessageWithIPv6AddressAttribute) {
+  StunMessage msg;
+  size_t size = sizeof(kStunMessageWithIPv6MappedAddress);
+
+  talk_base::IPAddress test_ip(kIPv6TestAddress1);
+
+  msg.SetType(STUN_BINDING_REQUEST);
+  msg.SetTransactionID(
+      std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+                  kStunTransactionIdLength));
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+  StunAddressAttribute* addr =
+      StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+  talk_base::SocketAddress test_addr(test_ip, kTestMessagePort2);
+  addr->SetAddress(test_addr);
+  msg.AddAttribute(addr);
+
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20));
+
+  talk_base::ByteBuffer out;
+  msg.Write(&out);
+  ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv6MappedAddress));
+  int len1 = out.Length();
+  std::string bytes;
+  out.ReadString(&bytes, len1);
+  ASSERT_EQ(0, std::memcmp(bytes.c_str(),
+                           kStunMessageWithIPv6MappedAddress,
+                           len1));
+}
+
+TEST_F(StunTest, WriteMessageWithIPv4AddressAttribute) {
+  StunMessage msg;
+  size_t size = sizeof(kStunMessageWithIPv4MappedAddress);
+
+  talk_base::IPAddress test_ip(kIPv4TestAddress1);
+
+  msg.SetType(STUN_BINDING_RESPONSE);
+  msg.SetTransactionID(
+      std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+                  kStunTransactionIdLength));
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+  StunAddressAttribute* addr =
+      StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+  talk_base::SocketAddress test_addr(test_ip, kTestMessagePort4);
+  addr->SetAddress(test_addr);
+  msg.AddAttribute(addr);
+
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+  talk_base::ByteBuffer out;
+  msg.Write(&out);
+  ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv4MappedAddress));
+  int len1 = out.Length();
+  std::string bytes;
+  out.ReadString(&bytes, len1);
+  ASSERT_EQ(0, std::memcmp(bytes.c_str(),
+                           kStunMessageWithIPv4MappedAddress,
+                           len1));
+}
+
+TEST_F(StunTest, WriteMessageWithIPv6XorAddressAttribute) {
+  StunMessage msg;
+  size_t size = sizeof(kStunMessageWithIPv6XorMappedAddress);
+
+  talk_base::IPAddress test_ip(kIPv6TestAddress1);
+
+  msg.SetType(STUN_BINDING_RESPONSE);
+  msg.SetTransactionID(
+      std::string(reinterpret_cast<const char*>(kTestTransactionId2),
+                  kStunTransactionIdLength));
+  CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+
+  StunAddressAttribute* addr =
+      StunAttribute::CreateAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  talk_base::SocketAddress test_addr(test_ip, kTestMessagePort1);
+  addr->SetAddress(test_addr);
+  msg.AddAttribute(addr);
+
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+  talk_base::ByteBuffer out;
+  msg.Write(&out);
+  ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv6XorMappedAddress));
+  int len1 = out.Length();
+  std::string bytes;
+  out.ReadString(&bytes, len1);
+  ASSERT_EQ(0, std::memcmp(bytes.c_str(),
+                           kStunMessageWithIPv6XorMappedAddress,
+                           len1));
+}
+
+TEST_F(StunTest, WriteMessageWithIPv4XoreAddressAttribute) {
+  StunMessage msg;
+  size_t size = sizeof(kStunMessageWithIPv4XorMappedAddress);
+
+  talk_base::IPAddress test_ip(kIPv4TestAddress1);
+
+  msg.SetType(STUN_BINDING_RESPONSE);
+  msg.SetTransactionID(
+      std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+                  kStunTransactionIdLength));
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+  StunAddressAttribute* addr =
+      StunAttribute::CreateAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  talk_base::SocketAddress test_addr(test_ip, kTestMessagePort3);
+  addr->SetAddress(test_addr);
+  msg.AddAttribute(addr);
+
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+  talk_base::ByteBuffer out;
+  msg.Write(&out);
+  ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv4XorMappedAddress));
+  int len1 = out.Length();
+  std::string bytes;
+  out.ReadString(&bytes, len1);
+  ASSERT_EQ(0, std::memcmp(bytes.c_str(),
+                           kStunMessageWithIPv4XorMappedAddress,
+                           len1));
+}
+
+TEST_F(StunTest, ReadByteStringAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithByteStringAttribute);
+
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+  CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+  const StunByteStringAttribute* username =
+      msg.GetByteString(STUN_ATTR_USERNAME);
+  ASSERT_EQ(0, std::memcmp(kTestUserName1, username->bytes(),
+                           username->length()));
+}
+
+TEST_F(StunTest, ReadPaddedByteStringAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg,
+                                kStunMessageWithPaddedByteStringAttribute);
+  ASSERT_NE(0U, size);
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+  CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+  const StunByteStringAttribute* username =
+      msg.GetByteString(STUN_ATTR_USERNAME);
+  ASSERT_EQ(0, std::memcmp(kTestUserName2, username->bytes(),
+                           username->length()));
+}
+
+TEST_F(StunTest, ReadErrorCodeAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithErrorAttribute);
+
+  CheckStunHeader(msg, STUN_ALLOCATE_ERROR_RESPONSE, size);
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+  const StunErrorCodeAttribute* errorcode = msg.GetErrorCode();
+  ASSERT_EQ(kTestErrorClass, errorcode->error_class());
+  ASSERT_EQ(kTestErrorNumber, errorcode->number());
+  std::string reason = errorcode->reason();
+  ASSERT_EQ(0, strcmp(reason.c_str(), kTestErrorReason));
+}
+
+TEST_F(StunTest, ReadMessageWithAnUnknownAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithUnknownAttribute);
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+
+  // Parsing should have succeeded and there should be a lifetime attribute
+  const StunUInt32Attribute* uval = msg.GetUInt32(STUN_ATTR_LIFETIME);
+  EXPECT_TRUE(uval != NULL);
+  if (uval != NULL) {
+    EXPECT_EQ(11U, uval->value());
+  }
+}
+
+TEST_F(StunTest, ReadMessageWithAUInt16ListAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithUInt16ListAttribute);
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+  const StunUInt16ListAttribute* types = msg.GetUnknownAttributes();
+  EXPECT_EQ(3U, types->Size());
+  EXPECT_EQ(0x1U, types->GetType(0));
+  EXPECT_EQ(0x1000U, types->GetType(1));
+  EXPECT_EQ(0xAB0CU, types->GetType(2));
+}
+
+TEST_F(StunTest, WriteMessageWithAUInt16ListAttribute) {
+  StunMessage msg;
+  size_t size = sizeof(kStunMessageWithUInt16ListAttribute);
+
+  msg.SetType(STUN_BINDING_REQUEST);
+  msg.SetTransactionID(
+      std::string(reinterpret_cast<const char*>(kTestTransactionId2),
+                  kStunTransactionIdLength));
+  CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+  StunUInt16ListAttribute* list = StunAttribute::CreateUnknownAttributes();
+  list->AddType(0x1U);
+  list->AddType(0x1000U);
+  list->AddType(0xAB0CU);
+  msg.AddAttribute(list);
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20));
+
+  talk_base::ByteBuffer out;
+  msg.Write(&out);
+  ASSERT_EQ(size, out.Length());
+  // Check everything up to the padding.
+  ASSERT_EQ(0, std::memcmp(out.Data(), kStunMessageWithUInt16ListAttribute,
+                           size - 2));
+}
+
+void CheckFailureToRead(const unsigned char* testcase, size_t length) {
+  StunMessage msg;
+  const char* input = reinterpret_cast<const char*>(testcase);
+  talk_base::ByteBuffer buf(input, length);
+  ASSERT_FALSE(msg.Read(&buf));
+}
+
+TEST_F(StunTest, FailToReadInvalidMessages) {
+  CheckFailureToRead(kStunMessageWithZeroLength,
+                     kRealLengthOfInvalidLengthTestCases);
+  CheckFailureToRead(kStunMessageWithSmallLength,
+                     kRealLengthOfInvalidLengthTestCases);
+  CheckFailureToRead(kStunMessageWithExcessLength,
+                     kRealLengthOfInvalidLengthTestCases);
+}
+
+#undef ReadStunMessage
+
+// Test that we don't care what order we set the parts of an address
+TEST_F(StunTest, CreateAddressInArbitraryOrder) {
+  StunAddressAttribute* addr =
+    StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+  // Port first
+  addr->SetPort(kTestMessagePort1);
+  addr->SetIP(talk_base::IPAddress(kIPv4TestAddress1));
+  ASSERT_EQ(kTestMessagePort1, addr->port());
+  ASSERT_EQ(talk_base::IPAddress(kIPv4TestAddress1), addr->ipaddr());
+
+  StunAddressAttribute* addr2 =
+    StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+  // IP first
+  addr2->SetIP(talk_base::IPAddress(kIPv4TestAddress1));
+  addr2->SetPort(kTestMessagePort2);
+  ASSERT_EQ(kTestMessagePort2, addr2->port());
+  ASSERT_EQ(talk_base::IPAddress(kIPv4TestAddress1), addr2->ipaddr());
+
+  delete addr;
+  delete addr2;
+}
+
+// Legacy test bodies
+static void DoTest(const char* input, size_t size, const char* transaction_id) {
+  StunMessage msg, msg2;
+  in_addr legacy_in_addr;
+  legacy_in_addr.s_addr = htonl(17U);
+  talk_base::IPAddress legacy_ip(legacy_in_addr);
+
+  talk_base::ByteBuffer buf(input, size);
+  EXPECT_TRUE(msg.Read(&buf));
+
+  EXPECT_EQ(STUN_BINDING_REQUEST, msg.type());
+  EXPECT_EQ(size - 20, msg.length());
+  EXPECT_EQ(transaction_id, msg.transaction_id());
+
+  msg2.SetType(STUN_BINDING_REQUEST);
+  msg2.SetTransactionID(transaction_id);
+
+  const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+  ASSERT_TRUE(addr != NULL);
+  EXPECT_EQ(1, addr->family());
+  EXPECT_EQ(13, addr->port());
+  EXPECT_EQ(legacy_ip, addr->ipaddr());
+
+  StunAddressAttribute* addr2 =
+      StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+  addr2->SetPort(13);
+  addr2->SetIP(legacy_ip);
+  msg2.AddAttribute(addr2);
+
+  const StunByteStringAttribute* bytes = msg.GetByteString(STUN_ATTR_USERNAME);
+  ASSERT_TRUE(bytes != NULL);
+  EXPECT_EQ(12, bytes->length());
+  EXPECT_EQ(0, std::memcmp(bytes->bytes(), "abcdefghijkl", bytes->length()));
+
+  StunByteStringAttribute* bytes2 =
+      StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+  bytes2->CopyBytes("abcdefghijkl");
+  msg2.AddAttribute(bytes2);
+
+  bytes = msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+  ASSERT_TRUE(bytes != NULL);
+  EXPECT_EQ(20, bytes->length());
+  EXPECT_EQ(0, std::memcmp(bytes->bytes(),
+                      "abcdefghijklmnopqrst",
+                      bytes->length()));
+
+  bytes2 = StunAttribute::CreateByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+  bytes2->CopyBytes("abcdefghijklmnopqrst");
+  msg2.AddAttribute(bytes2);
+
+  const StunErrorCodeAttribute* ecode = msg.GetErrorCode();
+  ASSERT_TRUE(ecode != NULL);
+  EXPECT_EQ(2, ecode->error_class());
+  EXPECT_EQ(10, ecode->number());
+  EXPECT_EQ("foo bar!", ecode->reason());
+
+  StunErrorCodeAttribute* ecode2 = StunAttribute::CreateErrorCode();
+  ecode2->SetErrorClass(2);
+  ecode2->SetNumber(10);
+  ecode2->SetReason("foo bar!");
+  msg2.AddAttribute(ecode2);
+
+  const StunUInt16ListAttribute* unknown = msg.GetUnknownAttributes();
+  ASSERT_TRUE(unknown != NULL);
+  EXPECT_EQ(2U, unknown->Size());
+  EXPECT_EQ(1U, unknown->GetType(0));
+  EXPECT_EQ(2U, unknown->GetType(1));
+
+  StunUInt16ListAttribute* unknown2 = StunAttribute::CreateUnknownAttributes();
+  unknown2->AddType(1);
+  unknown2->AddType(2);
+  msg2.AddAttribute(unknown2);
+
+  const StunUInt32Attribute* uval = msg.GetUInt32(STUN_ATTR_LIFETIME);
+  ASSERT_TRUE(uval != NULL);
+  EXPECT_EQ(11U, uval->value());
+
+  StunUInt32Attribute* uval2 = StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
+  uval2->SetValue(11);
+  msg2.AddAttribute(uval2);
+
+  bytes = msg.GetByteString(STUN_ATTR_MAGIC_COOKIE);
+  ASSERT_TRUE(bytes != NULL);
+  EXPECT_EQ(4, bytes->length());
+  EXPECT_EQ(0, std::memcmp(bytes->bytes(), TURN_MAGIC_COOKIE_VALUE,
+                           sizeof(TURN_MAGIC_COOKIE_VALUE)));
+
+  bytes2 = StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+  bytes2->CopyBytes(reinterpret_cast<const char*>(TURN_MAGIC_COOKIE_VALUE),
+                    sizeof(TURN_MAGIC_COOKIE_VALUE));
+  msg2.AddAttribute(bytes2);
+
+  uval = msg.GetUInt32(STUN_ATTR_BANDWIDTH);
+  ASSERT_TRUE(uval != NULL);
+  EXPECT_EQ(6U, uval->value());
+
+  uval2 = StunAttribute::CreateUInt32(STUN_ATTR_BANDWIDTH);
+  uval2->SetValue(6);
+  msg2.AddAttribute(uval2);
+
+  addr = msg.GetAddress(STUN_ATTR_DESTINATION_ADDRESS);
+  ASSERT_TRUE(addr != NULL);
+  EXPECT_EQ(1, addr->family());
+  EXPECT_EQ(13, addr->port());
+  EXPECT_EQ(legacy_ip, addr->ipaddr());
+
+  addr2 = StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+  addr2->SetPort(13);
+  addr2->SetIP(legacy_ip);
+  msg2.AddAttribute(addr2);
+
+  addr = msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+  ASSERT_TRUE(addr != NULL);
+  EXPECT_EQ(1, addr->family());
+  EXPECT_EQ(13, addr->port());
+  EXPECT_EQ(legacy_ip, addr->ipaddr());
+
+  addr2 = StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2);
+  addr2->SetPort(13);
+  addr2->SetIP(legacy_ip);
+  msg2.AddAttribute(addr2);
+
+  bytes = msg.GetByteString(STUN_ATTR_DATA);
+  ASSERT_TRUE(bytes != NULL);
+  EXPECT_EQ(7, bytes->length());
+  EXPECT_EQ(0, std::memcmp(bytes->bytes(), "abcdefg", bytes->length()));
+
+  bytes2 = StunAttribute::CreateByteString(STUN_ATTR_DATA);
+  bytes2->CopyBytes("abcdefg");
+  msg2.AddAttribute(bytes2);
+
+  talk_base::ByteBuffer out;
+  msg.Write(&out);
+  EXPECT_EQ(size, out.Length());
+  size_t len1 = out.Length();
+  std::string outstring;
+  out.ReadString(&outstring, len1);
+  EXPECT_EQ(0, std::memcmp(outstring.c_str(), input, len1));
+
+  talk_base::ByteBuffer out2;
+  msg2.Write(&out2);
+  EXPECT_EQ(size, out2.Length());
+  size_t len2 = out2.Length();
+  std::string outstring2;
+  out2.ReadString(&outstring2, len2);
+  EXPECT_EQ(0, std::memcmp(outstring2.c_str(), input, len2));
+}
+
+TEST_F(StunTest, TestStunPacket) {
+  DoTest(reinterpret_cast<const char*>(kStunMessageWithManyAttributes),
+         sizeof(kStunMessageWithManyAttributes),
+         "0123456789ab");
+}
+
+TEST_F(StunTest, TestRejectsRtcpPacket) {
+  StunMessage msg;
+
+  talk_base::ByteBuffer buf(
+      reinterpret_cast<const char*>(kRtcpPacket), sizeof(kRtcpPacket));
+  EXPECT_FALSE(msg.Read(&buf));
+}
+
+TEST_F(StunTest, TestLegacyPacket) {
+  // The RFC3489 packet in this test is the same as
+  // kStunMessageWithManyAttributes, but with a different value where the
+  // magic cookie was.
+  talk_base::scoped_array<char>
+      rfc3489_packet(new char[sizeof(kStunMessageWithManyAttributes)]);
+  memcpy(rfc3489_packet.get(), kStunMessageWithManyAttributes,
+         sizeof(kStunMessageWithManyAttributes));
+  // Overwrites the magic cookie here.
+  memcpy(&rfc3489_packet[4], &kStunMessageWithManyAttributes[8], 4);
+
+  DoTest(reinterpret_cast<const char*>(rfc3489_packet.get()),
+         sizeof(kStunMessageWithManyAttributes), "01230123456789ab");
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/stunport.cc b/talk/p2p/base/stunport.cc
index ba797ff..7328b99 100644
--- a/talk/p2p/base/stunport.cc
+++ b/talk/p2p/base/stunport.cc
@@ -66,7 +66,7 @@
     } else if (addr_attr->family() != 1) {
       LOG(LS_ERROR) << "Binding address has bad family";
     } else {
-      talk_base::SocketAddress addr(addr_attr->ip(), addr_attr->port());
+      talk_base::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
       port_->AddAddress(addr, "udp", true);
     }
 
@@ -127,7 +127,7 @@
 StunPort::StunPort(talk_base::Thread* thread,
                    talk_base::PacketSocketFactory* factory,
                    talk_base::Network* network,
-                   uint32 ip, int min_port, int max_port,
+                   const talk_base::IPAddress& ip, int min_port, int max_port,
                    const talk_base::SocketAddress& server_addr)
     : Port(thread, STUN_PORT_TYPE, factory, network, ip, min_port, max_port),
       server_addr_(server_addr),
diff --git a/talk/p2p/base/stunport.h b/talk/p2p/base/stunport.h
index 26f46a5..c74ad4d 100644
--- a/talk/p2p/base/stunport.h
+++ b/talk/p2p/base/stunport.h
@@ -49,7 +49,8 @@
   static StunPort* Create(talk_base::Thread* thread,
                           talk_base::PacketSocketFactory* factory,
                           talk_base::Network* network,
-                          uint32 ip, int min_port, int max_port,
+                          const talk_base::IPAddress& ip,
+                          int min_port, int max_port,
                           const talk_base::SocketAddress& server_addr) {
     StunPort* port = new StunPort(thread, factory, network,
                                   ip, min_port, max_port, server_addr);
@@ -88,7 +89,8 @@
 
  protected:
   StunPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
-           talk_base::Network* network, uint32 ip, int min_port, int max_port,
+           talk_base::Network* network, const talk_base::IPAddress& ip,
+           int min_port, int max_port,
            const talk_base::SocketAddress& server_addr);
   bool Init();
 
diff --git a/talk/p2p/base/stunport_unittest.cc b/talk/p2p/base/stunport_unittest.cc
new file mode 100644
index 0000000..3d830ba
--- /dev/null
+++ b/talk/p2p/base/stunport_unittest.cc
@@ -0,0 +1,141 @@
+/*
+ * libjingle
+ * Copyright 2009 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/teststunserver.h"
+
+using talk_base::SocketAddress;
+
+static const SocketAddress kLocalAddr("127.0.0.1", 0);
+static const SocketAddress kStunAddr("127.0.0.1", 5000);
+static const SocketAddress kBadAddr("0.0.0.1", 5000);
+static const SocketAddress kStunHostnameAddr("localhost", 5000);
+static const SocketAddress kBadHostnameAddr("not-a-real-hostname", 5000);
+static const int kTimeoutMs = 10000;
+
+// Tests connecting a StunPort to a fake STUN server (cricket::StunServer)
+// TODO: Use a VirtualSocketServer here. We have to use a
+// PhysicalSocketServer right now since DNS is not part of SocketServer yet.
+class StunPortTest : public testing::Test,
+                     public sigslot::has_slots<> {
+ public:
+  StunPortTest()
+      : network_("unittest", "unittest", talk_base::IPAddress(INADDR_ANY)),
+        socket_factory_(talk_base::Thread::Current()),
+        stun_server_(new cricket::TestStunServer(
+          talk_base::Thread::Current(), kStunAddr)),
+        done_(false), error_(false) {
+  }
+
+  const cricket::Port* port() const { return stun_port_.get(); }
+  bool done() const { return done_; }
+  bool error() const { return error_; }
+
+  void CreateStunPort(const talk_base::SocketAddress& server_addr) {
+    stun_port_.reset(cricket::StunPort::Create(
+        talk_base::Thread::Current(), &socket_factory_, &network_,
+        kLocalAddr.ipaddr(), 0, 0, server_addr));
+    stun_port_->SignalAddressReady.connect(this,
+        &StunPortTest::OnAddressReady);
+    stun_port_->SignalAddressError.connect(this,
+        &StunPortTest::OnAddressError);
+  }
+
+  void PrepareAddress() {
+    stun_port_->PrepareAddress();
+  }
+
+ protected:
+  static void SetUpTestCase() {
+    // Ensure the RNG is inited.
+    talk_base::InitRandom(NULL, 0);
+  }
+
+  void OnAddressReady(cricket::Port* port) {
+    done_ = true;
+    error_ = false;
+  }
+  void OnAddressError(cricket::Port* port) {
+    done_ = true;
+    error_ = true;
+  }
+
+ private:
+  talk_base::Network network_;
+  talk_base::BasicPacketSocketFactory socket_factory_;
+  talk_base::scoped_ptr<cricket::StunPort> stun_port_;
+  talk_base::scoped_ptr<cricket::TestStunServer> stun_server_;
+  bool done_;
+  bool error_;
+};
+
+// Test that we can create a STUN port
+TEST_F(StunPortTest, TestBasic) {
+  CreateStunPort(kStunAddr);
+  EXPECT_EQ("stun", port()->type());
+  EXPECT_EQ(0U, port()->candidates().size());
+}
+
+// Test that we can get an address from a STUN server.
+TEST_F(StunPortTest, TestPrepareAddress) {
+  CreateStunPort(kStunAddr);
+  PrepareAddress();
+  EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+  ASSERT_EQ(1U, port()->candidates().size());
+  EXPECT_TRUE(kLocalAddr.EqualIPs(port()->candidates()[0].address()));
+}
+
+// Test that we fail properly if we can't get an address.
+TEST_F(StunPortTest, TestPrepareAddressFail) {
+  CreateStunPort(kBadAddr);
+  PrepareAddress();
+  EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+  EXPECT_TRUE(error());
+  EXPECT_EQ(0U, port()->candidates().size());
+}
+
+// Test that we can get an address from a STUN server specified by a hostname.
+TEST_F(StunPortTest, TestPrepareAddressHostname) {
+  CreateStunPort(kStunHostnameAddr);
+  PrepareAddress();
+  EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+  ASSERT_EQ(1U, port()->candidates().size());
+  EXPECT_TRUE(kLocalAddr.EqualIPs(port()->candidates()[0].address()));
+}
+
+// Test that we handle hostname lookup failures properly.
+TEST_F(StunPortTest, TestPrepareAddressHostnameFail) {
+  CreateStunPort(kBadHostnameAddr);
+  PrepareAddress();
+  EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+  EXPECT_TRUE(error());
+  EXPECT_EQ(0U, port()->candidates().size());
+}
diff --git a/talk/p2p/base/stunrequest_unittest.cc b/talk/p2p/base/stunrequest_unittest.cc
new file mode 100644
index 0000000..524c5e6
--- /dev/null
+++ b/talk/p2p/base/stunrequest_unittest.cc
@@ -0,0 +1,225 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/timeutils.h"
+#include "talk/p2p/base/stunrequest.h"
+
+using namespace cricket;
+
+class StunRequestTest : public testing::Test,
+                        public sigslot::has_slots<> {
+ public:
+  static void SetUpTestCase() {
+    talk_base::InitRandom(NULL, 0);
+  }
+  StunRequestTest()
+      : manager_(talk_base::Thread::Current()),
+        request_count_(0), response_(NULL),
+        success_(false), failure_(false), timeout_(false) {
+    manager_.SignalSendPacket.connect(this, &StunRequestTest::OnSendPacket);
+  }
+
+  void OnSendPacket(const void* data, size_t size, StunRequest* req) {
+    request_count_++;
+  }
+
+  void OnResponse(StunMessage* res) {
+    response_ = res;
+    success_ = true;
+  }
+  void OnErrorResponse(StunMessage* res) {
+    response_ = res;
+    failure_ = true;
+  }
+  void OnTimeout() {
+    timeout_ = true;
+  }
+
+ protected:
+  static StunMessage* CreateStunMessage(StunMessageType type,
+                                        StunMessage* req) {
+    StunMessage* msg = new StunMessage();
+    msg->SetType(type);
+    if (req) {
+      msg->SetTransactionID(req->transaction_id());
+    } else {
+      msg->SetTransactionID(
+          talk_base::CreateRandomString(kStunTransactionIdLength));
+    }
+    return msg;
+  }
+  static int TotalDelay(int sends) {
+    int total = 0;
+    for (int i = 0; i < sends; i++) {
+      if (i < 4)
+        total += 100 << i;
+      else
+        total += 1600;
+    }
+    return total;
+  }
+
+  StunRequestManager manager_;
+  int request_count_;
+  StunMessage* response_;
+  bool success_;
+  bool failure_;
+  bool timeout_;
+};
+
+// Forwards results to the test class.
+class StunRequestThunker : public StunRequest {
+ public:
+  StunRequestThunker(StunMessage* msg, StunRequestTest* test)
+      : StunRequest(msg), test_(test) {}
+  explicit StunRequestThunker(StunRequestTest* test) : test_(test) {}
+ private:
+  virtual void OnResponse(StunMessage* res) {
+    test_->OnResponse(res);
+  }
+  virtual void OnErrorResponse(StunMessage* res) {
+    test_->OnErrorResponse(res);
+  }
+  virtual void OnTimeout() {
+    test_->OnTimeout();
+  }
+
+  virtual void Prepare(StunMessage* request) {
+    request->SetType(STUN_BINDING_REQUEST);
+  }
+
+  StunRequestTest* test_;
+};
+
+// Test handling of a normal binding response.
+TEST_F(StunRequestTest, TestSuccess) {
+  StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+  StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+
+  manager_.Send(new StunRequestThunker(req, this));
+  EXPECT_TRUE(manager_.CheckResponse(res));
+
+  EXPECT_TRUE(response_ == res);
+  EXPECT_TRUE(success_);
+  EXPECT_FALSE(failure_);
+  EXPECT_FALSE(timeout_);
+  delete res;
+}
+
+// Test handling of an error binding response.
+TEST_F(StunRequestTest, TestError) {
+  StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+  StunMessage* res = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE, req);
+
+  manager_.Send(new StunRequestThunker(req, this));
+  EXPECT_TRUE(manager_.CheckResponse(res));
+
+  EXPECT_TRUE(response_ == res);
+  EXPECT_FALSE(success_);
+  EXPECT_TRUE(failure_);
+  EXPECT_FALSE(timeout_);
+  delete res;
+}
+
+// Test handling of a binding response with the wrong transaction id.
+TEST_F(StunRequestTest, TestUnexpected) {
+  StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+  StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, NULL);
+
+  manager_.Send(new StunRequestThunker(req, this));
+  EXPECT_FALSE(manager_.CheckResponse(res));
+
+  EXPECT_TRUE(response_ == NULL);
+  EXPECT_FALSE(success_);
+  EXPECT_FALSE(failure_);
+  EXPECT_FALSE(timeout_);
+  delete res;
+}
+
+// Test that requests are sent at the right times, and that the 9th request
+// (sent at 7900 ms) can be properly replied to.
+TEST_F(StunRequestTest, TestBackoff) {
+  StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+  StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+
+  uint32 start = talk_base::Time();
+  manager_.Send(new StunRequestThunker(req, this));
+  for (int i = 0; i < 9; ++i) {
+    while (request_count_ == i)
+      talk_base::Thread::Current()->ProcessMessages(1);
+    int32 elapsed = talk_base::TimeSince(start);
+    LOG(LS_INFO) << "STUN request #" << (i + 1)
+                 << " sent at " << elapsed << " ms";
+    EXPECT_GE(TotalDelay(i + 1), elapsed);
+  }
+  EXPECT_TRUE(manager_.CheckResponse(res));
+
+  EXPECT_TRUE(response_ == res);
+  EXPECT_TRUE(success_);
+  EXPECT_FALSE(failure_);
+  EXPECT_FALSE(timeout_);
+  delete res;
+}
+
+// Test that we timeout properly if no response is received in 9500 ms.
+TEST_F(StunRequestTest, TestTimeout) {
+  StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+  StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+
+  manager_.Send(new StunRequestThunker(req, this));
+  talk_base::Thread::Current()->ProcessMessages(10000);  // > STUN timeout
+  EXPECT_FALSE(manager_.CheckResponse(res));
+
+  EXPECT_TRUE(response_ == NULL);
+  EXPECT_FALSE(success_);
+  EXPECT_FALSE(failure_);
+  EXPECT_TRUE(timeout_);
+  delete res;
+}
+
+// Regression test for specific crash where we receive a response with the
+// same id as a request that doesn't have an underlying StunMessage yet.
+TEST_F(StunRequestTest, TestNoEmptyRequest) {
+  StunRequestThunker* request = new StunRequestThunker(this);
+
+  manager_.SendDelayed(request, 100);
+
+  StunMessage dummy_req;
+  dummy_req.SetTransactionID(request->id());
+  StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, &dummy_req);
+
+  EXPECT_TRUE(manager_.CheckResponse(res));
+
+  EXPECT_TRUE(response_ == res);
+  EXPECT_TRUE(success_);
+  EXPECT_FALSE(failure_);
+  EXPECT_FALSE(timeout_);
+  delete res;
+}
diff --git a/talk/p2p/base/stunserver.cc b/talk/p2p/base/stunserver.cc
index f34fd2d..8a5d447 100644
--- a/talk/p2p/base/stunserver.cc
+++ b/talk/p2p/base/stunserver.cc
@@ -96,7 +96,7 @@
     mapped_addr = StunAttribute::CreateAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
   }
   mapped_addr->SetPort(remote_addr.port());
-  mapped_addr->SetIP(remote_addr.ip());
+  mapped_addr->SetIP(remote_addr.ipaddr());
   response.AddAttribute(mapped_addr);
 
   // TODO: Add username and message-integrity.
diff --git a/talk/p2p/base/stunserver_unittest.cc b/talk/p2p/base/stunserver_unittest.cc
new file mode 100644
index 0000000..b7f2f00
--- /dev/null
+++ b/talk/p2p/base/stunserver_unittest.cc
@@ -0,0 +1,129 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/base/testclient.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/stunserver.h"
+
+using namespace cricket;
+
+static const talk_base::SocketAddress server_addr("99.99.99.1", 3478);
+static const talk_base::SocketAddress client_addr("1.2.3.4", 1234);
+
+class StunServerTest : public testing::Test {
+ public:
+  StunServerTest()
+    : pss_(new talk_base::PhysicalSocketServer),
+      ss_(new talk_base::VirtualSocketServer(pss_.get())),
+      worker_(ss_.get()) {
+  }
+  virtual void SetUp() {
+    server_.reset(new StunServer(
+        talk_base::AsyncUDPSocket::Create(ss_.get(), server_addr)));
+    client_.reset(new talk_base::TestClient(
+        talk_base::AsyncUDPSocket::Create(ss_.get(), client_addr)));
+
+    worker_.Start();
+  }
+  void Send(const StunMessage& msg) {
+    talk_base::ByteBuffer buf;
+    msg.Write(&buf);
+    Send(buf.Data(), buf.Length());
+  }
+  void Send(const char* buf, int len) {
+    client_->SendTo(buf, len, server_addr);
+  }
+  StunMessage* Receive() {
+    StunMessage* msg = NULL;
+    talk_base::TestClient::Packet* packet = client_->NextPacket();
+    if (packet) {
+      talk_base::ByteBuffer buf(packet->buf, packet->size);
+      msg = new StunMessage();
+      msg->Read(&buf);
+      delete packet;
+    }
+    return msg;
+  }
+ private:
+  talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
+  talk_base::scoped_ptr<talk_base::VirtualSocketServer> ss_;
+  talk_base::Thread worker_;
+  talk_base::scoped_ptr<StunServer> server_;
+  talk_base::scoped_ptr<talk_base::TestClient> client_;
+};
+
+TEST_F(StunServerTest, TestGood) {
+  StunMessage req;
+  std::string transaction_id = "0123456789ab";
+  req.SetType(STUN_BINDING_REQUEST);
+  req.SetTransactionID(transaction_id);
+  Send(req);
+
+  StunMessage* msg = Receive();
+  ASSERT_TRUE(msg != NULL);
+  EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+  EXPECT_EQ(req.transaction_id(), msg->transaction_id());
+
+  const StunAddressAttribute* mapped_addr =
+      msg->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+  EXPECT_TRUE(mapped_addr != NULL);
+  EXPECT_EQ(1, mapped_addr->family());
+  EXPECT_EQ(client_addr.port(), mapped_addr->port());
+  if (mapped_addr->ipaddr() != client_addr.ipaddr()) {
+    LOG(LS_WARNING) << "Warning: mapped IP ("
+                    << mapped_addr->ipaddr()
+                    << ") != local IP (" << client_addr.ipaddr()
+                    << ")";
+  }
+
+  delete msg;
+}
+
+TEST_F(StunServerTest, TestBad) {
+  const char* bad = "this is a completely nonsensical message whose only "
+                    "purpose is to make the parser go 'ack'.  it doesn't "
+                    "look anything like a normal stun message";
+  Send(bad, std::strlen(bad));
+
+  StunMessage* msg = Receive();
+  ASSERT_TRUE(msg != NULL);
+  EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
+
+  const StunErrorCodeAttribute* err = msg->GetErrorCode();
+  EXPECT_TRUE(err != NULL);
+  EXPECT_EQ(4, err->error_class());
+  EXPECT_EQ(0, err->number());
+  EXPECT_EQ("Bad Request", err->reason());
+
+  delete msg;
+}
diff --git a/talk/p2p/base/tcpport.cc b/talk/p2p/base/tcpport.cc
index 555ba73..4db4761 100644
--- a/talk/p2p/base/tcpport.cc
+++ b/talk/p2p/base/tcpport.cc
@@ -35,7 +35,7 @@
 
 TCPPort::TCPPort(talk_base::Thread* thread,
                  talk_base::PacketSocketFactory* factory,
-                 talk_base::Network* network, uint32 ip,
+                 talk_base::Network* network, const talk_base::IPAddress& ip,
                  int min_port, int max_port, bool allow_listen)
     : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port),
       incoming_only_(false),
@@ -218,7 +218,7 @@
     }
   } else {
     // Incoming connections should match the network address.
-    ASSERT(socket_->GetLocalAddress().ip() == port->ip_);
+    ASSERT(socket_->GetLocalAddress().ipaddr() == port->ip_);
   }
 
   if (socket_) {
diff --git a/talk/p2p/base/tcpport.h b/talk/p2p/base/tcpport.h
index 8345b12..5ce917d 100644
--- a/talk/p2p/base/tcpport.h
+++ b/talk/p2p/base/tcpport.h
@@ -50,7 +50,8 @@
   static TCPPort* Create(talk_base::Thread* thread,
                          talk_base::PacketSocketFactory* factory,
                          talk_base::Network* network,
-                         uint32 ip, int min_port, int max_port,
+                         const talk_base::IPAddress& ip,
+                         int min_port, int max_port,
                          bool allow_listen) {
     TCPPort* port = new TCPPort(thread, factory, network,
                                 ip, min_port, max_port, allow_listen);
@@ -73,8 +74,8 @@
 
  protected:
   TCPPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
-          talk_base::Network* network, uint32 ip, int min_port, int max_port,
-          bool allow_listen);
+          talk_base::Network* network, const talk_base::IPAddress& ip,
+          int min_port, int max_port, bool allow_listen);
   bool Init();
 
   // Handles sending using the local TCP socket.
diff --git a/talk/p2p/base/testrelayserver.h b/talk/p2p/base/testrelayserver.h
new file mode 100644
index 0000000..45c2b7c
--- /dev/null
+++ b/talk/p2p/base/testrelayserver.h
@@ -0,0 +1,118 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_TESTRELAYSERVER_H_
+#define TALK_P2P_BASE_TESTRELAYSERVER_H_
+
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/relayserver.h"
+
+namespace cricket {
+
+// A test relay server. Useful for unit tests.
+class TestRelayServer : public sigslot::has_slots<> {
+ public:
+  TestRelayServer(talk_base::Thread* thread,
+                  const talk_base::SocketAddress& udp_int_addr,
+                  const talk_base::SocketAddress& udp_ext_addr,
+                  const talk_base::SocketAddress& tcp_int_addr,
+                  const talk_base::SocketAddress& tcp_ext_addr,
+                  const talk_base::SocketAddress& ssl_int_addr,
+                  const talk_base::SocketAddress& ssl_ext_addr)
+      : server_(thread) {
+    server_.AddInternalSocket(talk_base::AsyncUDPSocket::Create(
+        thread->socketserver(), udp_int_addr));
+    server_.AddExternalSocket(talk_base::AsyncUDPSocket::Create(
+        thread->socketserver(), udp_ext_addr));
+
+    tcp_int_socket_.reset(CreateListenSocket(thread, tcp_int_addr));
+    tcp_ext_socket_.reset(CreateListenSocket(thread, tcp_ext_addr));
+    ssl_int_socket_.reset(CreateListenSocket(thread, ssl_int_addr));
+    ssl_ext_socket_.reset(CreateListenSocket(thread, ssl_ext_addr));
+  }
+  int GetConnectionCount() const {
+    return server_.GetConnectionCount();
+  }
+  talk_base::SocketAddressPair GetConnection(int connection) const {
+    return server_.GetConnection(connection);
+  }
+  bool HasConnection(const talk_base::SocketAddress& address) const {
+    return server_.HasConnection(address);
+  }
+
+ private:
+  talk_base::AsyncSocket* CreateListenSocket(talk_base::Thread* thread,
+      const talk_base::SocketAddress& addr) {
+    talk_base::AsyncSocket* socket =
+        thread->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+    socket->Bind(addr);
+    socket->Listen(5);
+    socket->SignalReadEvent.connect(this, &TestRelayServer::OnAccept);
+    return socket;
+  }
+  void OnAccept(talk_base::AsyncSocket* socket) {
+    bool external = (socket == tcp_ext_socket_.get() ||
+                     socket == ssl_ext_socket_.get());
+    bool ssl = (socket == ssl_int_socket_.get() ||
+                socket == ssl_ext_socket_.get());
+    talk_base::AsyncSocket* raw_socket = socket->Accept(NULL);
+    if (raw_socket) {
+      talk_base::AsyncTCPSocket* packet_socket = new talk_base::AsyncTCPSocket(
+          (!ssl) ? raw_socket :
+          new talk_base::AsyncSSLServerSocket(raw_socket), false);
+      if (!external) {
+        packet_socket->SignalClose.connect(this,
+            &TestRelayServer::OnInternalClose);
+        server_.AddInternalSocket(packet_socket);
+      } else {
+        packet_socket->SignalClose.connect(this,
+            &TestRelayServer::OnExternalClose);
+        server_.AddExternalSocket(packet_socket);
+      }
+    }
+  }
+  void OnInternalClose(talk_base::AsyncPacketSocket* socket, int error) {
+    server_.RemoveInternalSocket(socket);
+  }
+  void OnExternalClose(talk_base::AsyncPacketSocket* socket, int error) {
+    server_.RemoveExternalSocket(socket);
+  }
+ private:
+  cricket::RelayServer server_;
+  talk_base::scoped_ptr<talk_base::AsyncSocket> tcp_int_socket_;
+  talk_base::scoped_ptr<talk_base::AsyncSocket> tcp_ext_socket_;
+  talk_base::scoped_ptr<talk_base::AsyncSocket> ssl_int_socket_;
+  talk_base::scoped_ptr<talk_base::AsyncSocket> ssl_ext_socket_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TESTRELAYSERVER_H_
diff --git a/talk/p2p/base/teststunserver.h b/talk/p2p/base/teststunserver.h
new file mode 100644
index 0000000..4574bf8
--- /dev/null
+++ b/talk/p2p/base/teststunserver.h
@@ -0,0 +1,54 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_TESTSTUNSERVER_H_
+#define TALK_P2P_BASE_TESTSTUNSERVER_H_
+
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/stunserver.h"
+
+namespace cricket {
+
+// A test STUN server. Useful for unit tests.
+class TestStunServer {
+ public:
+  TestStunServer(talk_base::Thread* thread,
+                 const talk_base::SocketAddress& addr)
+      : socket_(thread->socketserver()->CreateAsyncSocket(SOCK_DGRAM)),
+        udp_socket_(talk_base::AsyncUDPSocket::Create(socket_, addr)),
+        server_(udp_socket_) {
+  }
+ private:
+  talk_base::AsyncSocket* socket_;
+  talk_base::AsyncUDPSocket* udp_socket_;
+  cricket::StunServer server_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TESTSTUNSERVER_H_
diff --git a/talk/p2p/base/transport_unittest.cc b/talk/p2p/base/transport_unittest.cc
new file mode 100644
index 0000000..7718484
--- /dev/null
+++ b/talk/p2p/base/transport_unittest.cc
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2011 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/p2ptransport.h"
+
+class TransportTest : public testing::Test,
+                      public sigslot::has_slots<> {
+ public:
+  TransportTest()
+      : thread_(talk_base::Thread::Current()),
+        transport_(new cricket::P2PTransport(thread_, thread_, NULL)),
+        connecting_signalled_(false) {
+    transport_->SignalConnecting.connect(this, &TransportTest::OnConnecting);
+  }
+
+ protected:
+  void OnConnecting(cricket::Transport* transport) {
+    connecting_signalled_ = true;
+  }
+
+  talk_base::Thread* thread_;
+  talk_base::scoped_ptr<cricket::P2PTransport> transport_;
+  bool connecting_signalled_;
+};
+
+TEST_F(TransportTest, TestDestroyAllClearsPosts) {
+  EXPECT_TRUE(transport_->CreateChannel("test", "media") != NULL);
+
+  transport_->ConnectChannels();
+  transport_->DestroyAllChannels();
+
+  thread_->ProcessMessages(0);
+  EXPECT_FALSE(connecting_signalled_);
+}
+
+TEST_F(TransportTest, TestConnectChannelsDoesSignal) {
+  EXPECT_TRUE(transport_->CreateChannel("test", "media") != NULL);
+  transport_->ConnectChannels();
+  EXPECT_FALSE(connecting_signalled_);
+
+  EXPECT_TRUE_WAIT(connecting_signalled_, 100);
+}
+
diff --git a/talk/p2p/base/transportchannel.h b/talk/p2p/base/transportchannel.h
index 2672e80..74d0107 100644
--- a/talk/p2p/base/transportchannel.h
+++ b/talk/p2p/base/transportchannel.h
@@ -47,6 +47,8 @@
         readable_(false), writable_(false) {}
   virtual ~TransportChannel() {}
 
+  // Returns the session id of this channel.
+  const std::string& session_id() const { return session_id_; }
   // Returns the name of this channel.
   const std::string& name() const { return name_; }
   const std::string& content_type() const { return content_type_; }
@@ -66,6 +68,12 @@
   // supported by all transport types.
   virtual int SetOption(talk_base::Socket::Option opt, int value) = 0;
 
+  // Sets session id which created this transport channel.
+  // This is called from TransportProxy::GetOrCreateImpl.
+  void set_session_id(const std::string& session_id) {
+    session_id_ = session_id;
+  }
+
   // Returns the most recent error that occurred on this channel.
   virtual int GetError() = 0;
 
@@ -95,7 +103,9 @@
   // Sets the writable state, signaling if necessary.
   void set_writable(bool writable);
 
+
  private:
+  std::string session_id_;
   std::string name_;
   std::string content_type_;
   bool readable_;
diff --git a/talk/p2p/base/transportchannelproxy.cc b/talk/p2p/base/transportchannelproxy.cc
index f87383e..4e035b1 100644
--- a/talk/p2p/base/transportchannelproxy.cc
+++ b/talk/p2p/base/transportchannelproxy.cc
@@ -34,15 +34,16 @@
 
 TransportChannelProxy::TransportChannelProxy(const std::string& name,
                                              const std::string& content_type)
-    : TransportChannel(name, content_type), impl_(NULL) {
+    : TransportChannel(name, content_type), impl_(NULL), owner_(false) {
 }
 
 TransportChannelProxy::~TransportChannelProxy() {
-  if (impl_)
+  if (owner_ && impl_)
     impl_->GetTransport()->DestroyChannel(impl_->name());
 }
 
-void TransportChannelProxy::SetImplementation(TransportChannelImpl* impl) {
+void TransportChannelProxy::SetImplementation(TransportChannelImpl* impl,
+                                              bool owner) {
   impl_ = impl;
   impl_->SignalReadableState.connect(
       this, &TransportChannelProxy::OnReadableState);
@@ -56,6 +57,7 @@
     impl_->SetOption(it->first, it->second);
   }
   pending_options_.clear();
+  owner_ = owner;
 }
 
 int TransportChannelProxy::SendPacket(const char* data, size_t len) {
diff --git a/talk/p2p/base/transportchannelproxy.h b/talk/p2p/base/transportchannelproxy.h
index 957c304..6923429 100644
--- a/talk/p2p/base/transportchannelproxy.h
+++ b/talk/p2p/base/transportchannelproxy.h
@@ -49,7 +49,10 @@
   TransportChannelImpl* impl() { return impl_; }
 
   // Sets the implementation to which we will proxy.
-  void SetImplementation(TransportChannelImpl* impl);
+  // Impl can be belong to some other TransportChannelProxy,
+  // in that case it should't try to delete. TODO - Remove this hack
+  // when ref count support is available.
+  void SetImplementation(TransportChannelImpl* impl, bool owner);
 
   // Implementation of the TransportChannel interface.  These simply forward to
   // the implementation.
@@ -62,6 +65,7 @@
   typedef std::pair<talk_base::Socket::Option, int> OptionPair;
   typedef std::vector<OptionPair> OptionList;
   TransportChannelImpl* impl_;
+  bool owner_;
   OptionList pending_options_;
 
   // Catch signals from the implementation channel.  These just forward to the
diff --git a/talk/p2p/base/udpport.cc b/talk/p2p/base/udpport.cc
index 8355b00..311fb45 100644
--- a/talk/p2p/base/udpport.cc
+++ b/talk/p2p/base/udpport.cc
@@ -38,7 +38,7 @@
 UDPPort::UDPPort(talk_base::Thread* thread,
                  talk_base::PacketSocketFactory* factory,
                  talk_base::Network* network,
-                 uint32 ip, int min_port, int max_port)
+                 const talk_base::IPAddress& ip, int min_port, int max_port)
     : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port),
       socket_(NULL),
       error_(0) {
diff --git a/talk/p2p/base/udpport.h b/talk/p2p/base/udpport.h
index d74d4a3..5d767bb 100644
--- a/talk/p2p/base/udpport.h
+++ b/talk/p2p/base/udpport.h
@@ -48,7 +48,8 @@
   static UDPPort* Create(talk_base::Thread* thread,
                          talk_base::PacketSocketFactory* factory,
                          talk_base::Network* network,
-                         uint32 ip, int min_port, int max_port) {
+                         const talk_base::IPAddress& ip,
+                         int min_port, int max_port) {
     UDPPort* port = new UDPPort(thread, factory, network,
                                 ip, min_port, max_port);
     if (!port->Init()) {
@@ -68,7 +69,8 @@
 
  protected:
   UDPPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
-          talk_base::Network* network, uint32 ip, int min_port, int max_port);
+          talk_base::Network* network, const talk_base::IPAddress& ip,
+          int min_port, int max_port);
   bool Init();
 
   // Handles sending using the local UDP socket.
diff --git a/talk/p2p/client/basicportallocator.cc b/talk/p2p/client/basicportallocator.cc
index 51d069e..b77d5ed 100644
--- a/talk/p2p/client/basicportallocator.cc
+++ b/talk/p2p/client/basicportallocator.cc
@@ -154,7 +154,7 @@
 
   BasicPortAllocatorSession* session_;
   talk_base::Network* network_;
-  uint32 ip_;
+  talk_base::IPAddress ip_;
   PortConfiguration* config_;
   bool running_;
   int step_;
@@ -229,8 +229,8 @@
     BasicPortAllocator *allocator,
     const std::string &name,
     const std::string &session_type)
-    : PortAllocatorSession(allocator->flags()), allocator_(allocator),
-      name_(name), session_type_(session_type), network_thread_(NULL),
+    : PortAllocatorSession(name, session_type, allocator->flags()),
+      allocator_(allocator), network_thread_(NULL),
       socket_factory_(allocator->socket_factory()), allocation_started_(false),
       network_manager_started_(false),
       running_(false) {
diff --git a/talk/p2p/client/basicportallocator.h b/talk/p2p/client/basicportallocator.h
index 0e1d00a..f38bac6 100644
--- a/talk/p2p/client/basicportallocator.h
+++ b/talk/p2p/client/basicportallocator.h
@@ -43,7 +43,7 @@
  public:
   BasicPortAllocator(talk_base::NetworkManager* network_manager,
                      talk_base::PacketSocketFactory* socket_factory);
-  BasicPortAllocator(talk_base::NetworkManager* network_manager);
+  explicit BasicPortAllocator(talk_base::NetworkManager* network_manager);
   BasicPortAllocator(talk_base::NetworkManager* network_manager,
                      const talk_base::SocketAddress& stun_server,
                      const talk_base::SocketAddress& relay_server_udp,
@@ -114,8 +114,6 @@
   ~BasicPortAllocatorSession();
 
   virtual BasicPortAllocator* allocator() { return allocator_; }
-  const std::string& name() const { return name_; }
-  const std::string& session_type() const { return session_type_; }
   talk_base::Thread* network_thread() { return network_thread_; }
   talk_base::PacketSocketFactory* socket_factory() { return socket_factory_; }
 
@@ -130,7 +128,7 @@
 
   // Adds a port configuration that is now ready.  Once we have one for each
   // network (or a timeout occurs), we will start allocating ports.
-  void ConfigReady(PortConfiguration* config);
+  virtual void ConfigReady(PortConfiguration* config);
 
   // MessageHandler.  Can be overriden if message IDs do not conflict.
   virtual void OnMessage(talk_base::Message *message);
@@ -154,8 +152,6 @@
   void OnShake();
 
   BasicPortAllocator* allocator_;
-  std::string name_;
-  std::string session_type_;
   talk_base::Thread* network_thread_;
   talk_base::scoped_ptr<talk_base::PacketSocketFactory> owned_socket_factory_;
   talk_base::PacketSocketFactory* socket_factory_;
diff --git a/talk/p2p/client/connectivitychecker.cc b/talk/p2p/client/connectivitychecker.cc
new file mode 100644
index 0000000..de97924
--- /dev/null
+++ b/talk/p2p/client/connectivitychecker.cc
@@ -0,0 +1,513 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+
+#include <string>
+
+#include "talk/p2p/client/connectivitychecker.h"
+
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/autodetectproxy.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/helpers.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/logging.h"
+#include "talk/base/proxydetect.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/common.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/stunport.h"
+
+namespace cricket {
+
+static const char kSessionTypeVideo[] =
+    "http://www.google.com/session/video";
+static const char kSessionNameRtp[] = "rtp";
+
+static const char kDefaultStunHostname[] = "stun.l.google.com";
+static const int kDefaultStunPort = 19302;
+
+// Default maximum time in milliseconds we will wait for connections.
+static const uint32 kDefaultTimeoutMs = 3000;
+
+enum {
+  MSG_START = 1,
+  MSG_STOP = 2,
+  MSG_TIMEOUT = 3,
+  MSG_SIGNAL_RESULTS = 4
+};
+
+class TestHttpPortAllocator : public HttpPortAllocator {
+ public:
+  TestHttpPortAllocator(talk_base::NetworkManager* network_manager,
+                        const std::string& user_agent,
+                        const std::string& relay_token) :
+      HttpPortAllocator(network_manager, user_agent) {
+    SetRelayToken(relay_token);
+  }
+  PortAllocatorSession* CreateSession(
+      const std::string& name, const std::string& session_type) {
+    return new TestHttpPortAllocatorSession(this, name, session_type,
+                                            stun_hosts(), relay_hosts(),
+                                            relay_token(), user_agent());
+  }
+};
+
+void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) {
+  SignalConfigReady(config, proxy_);
+}
+
+void TestHttpPortAllocatorSession::OnRequestDone(
+    talk_base::SignalThread* data) {
+  talk_base::AsyncHttpRequest* request =
+      static_cast<talk_base::AsyncHttpRequest*>(data);
+
+  // Tell the checker that the request is complete.
+  SignalRequestDone(request);
+
+  // Pass on the response to super class.
+  HttpPortAllocatorSession::OnRequestDone(data);
+}
+
+ConnectivityChecker::ConnectivityChecker(
+    talk_base::Thread* worker,
+    const std::string& jid,
+    const std::string& session_id,
+    const std::string& user_agent,
+    const std::string& relay_token,
+    const std::string& connection)
+    : worker_(worker),
+      jid_(jid),
+      session_id_(session_id),
+      user_agent_(user_agent),
+      relay_token_(relay_token),
+      connection_(connection),
+      proxy_detect_(NULL),
+      timeout_ms_(kDefaultTimeoutMs),
+      stun_address_(kDefaultStunHostname, kDefaultStunPort) {
+}
+
+ConnectivityChecker::~ConnectivityChecker() {
+  Stop();
+  nics_.clear();
+}
+
+bool ConnectivityChecker::Initialize() {
+  network_manager_.reset(CreateNetworkManager());
+  socket_factory_.reset(CreateSocketFactory(worker_));
+  port_allocator_.reset(CreatePortAllocator(network_manager_.get(),
+                                            user_agent_, relay_token_));
+  return true;
+}
+
+void ConnectivityChecker::Start() {
+  main_ = talk_base::Thread::Current();
+  worker_->Post(this, MSG_START);
+}
+
+void ConnectivityChecker::Stop() {
+  worker_->Post(this, MSG_STOP);
+}
+
+void ConnectivityChecker::CleanUp() {
+  ASSERT(worker_ == talk_base::Thread::Current());
+  worker_->Clear(this, MSG_TIMEOUT);
+  if (proxy_detect_) {
+    proxy_detect_->Release();
+    proxy_detect_ = NULL;
+  }
+
+  for (uint32 i = 0; i < sessions_.size(); ++i) {
+    delete sessions_[i];
+  }
+  sessions_.clear();
+  for (uint32 i = 0; i < ports_.size(); ++i) {
+    delete ports_[i];
+  }
+  ports_.clear();
+}
+
+bool ConnectivityChecker::AddNic(const talk_base::IPAddress& ip,
+                                 const talk_base::SocketAddress& proxy_addr) {
+  NicMap::iterator i = nics_.find(NicId(ip, proxy_addr));
+  if (i != nics_.end()) {
+    // Already have it.
+    return false;
+  }
+  uint32 now = talk_base::Time();
+  NicInfo info;
+  info.ip = ip;
+  info.proxy_info = GetProxyInfo();
+  info.stun.start_time_ms = now;
+  nics_.insert(std::pair<NicId, NicInfo>(NicId(ip, proxy_addr), info));
+  return true;
+}
+
+void ConnectivityChecker::SetProxyInfo(const talk_base::ProxyInfo& proxy_info) {
+  port_allocator_->set_proxy(user_agent_, proxy_info);
+  AllocatePorts();
+}
+
+talk_base::ProxyInfo ConnectivityChecker::GetProxyInfo() const {
+  talk_base::ProxyInfo proxy_info;
+  if (proxy_detect_) {
+    proxy_info = proxy_detect_->proxy();
+  }
+  return proxy_info;
+}
+
+void ConnectivityChecker::CheckNetworks() {
+  network_manager_->SignalNetworksChanged.connect(
+      this, &ConnectivityChecker::OnNetworksChanged);
+  network_manager_->StartUpdating();
+}
+
+void ConnectivityChecker::OnMessage(talk_base::Message *msg) {
+  switch (msg->message_id) {
+    case MSG_START:
+      ASSERT(worker_ == talk_base::Thread::Current());
+      worker_->PostDelayed(timeout_ms_, this, MSG_TIMEOUT);
+      CheckNetworks();
+      break;
+    case MSG_STOP:
+      // We were stopped, just close down without signaling.
+      OnCheckDone(false);
+      break;
+    case MSG_TIMEOUT:
+      // Close down and signal results.
+      OnCheckDone(true);
+      break;
+    case MSG_SIGNAL_RESULTS:
+      ASSERT(main_ == talk_base::Thread::Current());
+      SignalCheckDone(this);
+      break;
+    default:
+      LOG(LS_ERROR) << "Unknown message: " << msg->message_id;
+  }
+}
+
+void ConnectivityChecker::OnCheckDone(bool signal_results) {
+  // Clean up memory allocated by the worker thread.
+  CleanUp();
+
+  if (signal_results) {
+    main_->Post(this, MSG_SIGNAL_RESULTS);
+  }
+}
+
+void ConnectivityChecker::OnProxyDetect(talk_base::SignalThread* thread) {
+  ASSERT(worker_ == talk_base::Thread::Current());
+  if (proxy_detect_->proxy().type != talk_base::PROXY_NONE) {
+    SetProxyInfo(proxy_detect_->proxy());
+  }
+}
+
+void ConnectivityChecker::OnRequestDone(talk_base::AsyncHttpRequest* request) {
+  ASSERT(worker_ == talk_base::Thread::Current());
+  // Since we don't know what nic were actually used for the http request,
+  // for now, just use the first one.
+  std::vector<talk_base::Network*> networks;
+  network_manager_->GetNetworks(&networks);
+  if (networks.empty()) {
+    LOG(LS_ERROR) << "No networks while registering http start.";
+    return;
+  }
+  talk_base::ProxyInfo proxy_info = request->proxy();
+  NicMap::iterator i = nics_.find(NicId(networks[0]->ip(), proxy_info.address));
+  if (i != nics_.end()) {
+    int port = request->port();
+    uint32 now = talk_base::Time();
+    NicInfo* nic_info = &i->second;
+    if (port == talk_base::HTTP_DEFAULT_PORT) {
+      nic_info->http.rtt = now - nic_info->http.start_time_ms;
+    } else if (port == talk_base::HTTP_SECURE_PORT) {
+      nic_info->https.rtt = now - nic_info->https.start_time_ms;
+    } else {
+      LOG(LS_ERROR) << "Got response with unknown port: " << port;
+    }
+  } else {
+    LOG(LS_ERROR) << "No nic info found while receiving response.";
+  }
+}
+
+void ConnectivityChecker::OnConfigReady(
+    const PortConfiguration* config,
+    const talk_base::ProxyInfo& proxy_info) {
+  ASSERT(worker_ == talk_base::Thread::Current());
+
+  // Since we send requests on both HTTP and HTTPS we will get two
+  // configs per nic. Results from the second will overwrite the
+  // result from the first.
+  // TODO: Handle multiple pings on one nic.
+  CreateRelayPorts(config, proxy_info);
+}
+
+void ConnectivityChecker::OnRelayAddressReady(Port* port) {
+  ASSERT(worker_ == talk_base::Thread::Current());
+  RelayPort* relay_port = reinterpret_cast<RelayPort*>(port);
+  const ProtocolAddress* address = relay_port->ServerAddress(0);
+  talk_base::IPAddress ip = port->network()->ip();
+  NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address));
+  if (i != nics_.end()) {
+    // We have it already, add the new information.
+    NicInfo* nic_info = &i->second;
+    ConnectInfo* connect_info = NULL;
+    if (address) {
+      switch (address->proto) {
+        case PROTO_UDP:
+          connect_info = &nic_info->udp;
+          break;
+        case PROTO_TCP:
+          connect_info = &nic_info->tcp;
+          break;
+        case PROTO_SSLTCP:
+          connect_info = &nic_info->ssltcp;
+          break;
+        default:
+          LOG(LS_ERROR) << " relay address with bad protocol added";
+      }
+      if (connect_info) {
+        connect_info->rtt =
+            talk_base::TimeSince(connect_info->start_time_ms);
+      }
+    }
+  } else {
+    LOG(LS_ERROR) << " got relay address for non-existing nic";
+  }
+}
+
+void ConnectivityChecker::OnStunAddressReady(Port* port) {
+  ASSERT(worker_ == talk_base::Thread::Current());
+  const std::vector<Candidate> candidates = port->candidates();
+  Candidate c = candidates[0];
+  talk_base::IPAddress ip = port->network()->ip();
+  NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address));
+  if (i != nics_.end()) {
+    // We have it already, add the new information.
+    uint32 now = talk_base::Time();
+    NicInfo* nic_info = &i->second;
+    nic_info->external_address = c.address();
+    nic_info->stun_server_address = static_cast<StunPort*>(port)->server_addr();
+    nic_info->stun.rtt = now - nic_info->stun.start_time_ms;
+  } else {
+    LOG(LS_ERROR) << "Got stun address for non-existing nic";
+  }
+}
+
+void ConnectivityChecker::OnStunAddressError(Port* port) {
+  ASSERT(worker_ == talk_base::Thread::Current());
+  LOG(LS_ERROR) << "Stun address error.";
+  talk_base::IPAddress ip = port->network()->ip();
+  NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address));
+  if (i != nics_.end()) {
+    // We have it already, add the new information.
+    NicInfo* nic_info = &i->second;
+    nic_info->stun_server_address = static_cast<StunPort*>(port)->server_addr();
+  }
+}
+
+void ConnectivityChecker::OnRelayAddressError(Port* port) {
+  ASSERT(worker_ == talk_base::Thread::Current());
+  LOG(LS_ERROR) << "Relay address error.";
+}
+
+void ConnectivityChecker::OnNetworksChanged() {
+  ASSERT(worker_ == talk_base::Thread::Current());
+  std::vector<talk_base::Network*> networks;
+  network_manager_->GetNetworks(&networks);
+  if (networks.empty()) {
+    LOG(LS_ERROR) << "Machine has no networks; nothing to do";
+    return;
+  }
+  AllocatePorts();
+}
+
+HttpPortAllocator* ConnectivityChecker::CreatePortAllocator(
+    talk_base::NetworkManager* network_manager,
+    const std::string& user_agent,
+    const std::string& relay_token) {
+  return new TestHttpPortAllocator(network_manager, user_agent, relay_token);
+}
+
+StunPort* ConnectivityChecker::CreateStunPort(
+    const PortConfiguration* config, talk_base::Network* network) {
+  return StunPort::Create(worker_,
+                          socket_factory_.get(),
+                          network,
+                          network->ip(),
+                          0,
+                          0,
+                          config->stun_address);
+}
+
+RelayPort* ConnectivityChecker::CreateRelayPort(
+    const PortConfiguration* config, talk_base::Network* network) {
+  return RelayPort::Create(worker_,
+                           socket_factory_.get(),
+                           network,
+                           network->ip(),
+                           port_allocator_->min_port(),
+                           port_allocator_->max_port(),
+                           config->username,
+                           config->password,
+                           config->magic_cookie);
+}
+
+void ConnectivityChecker::CreateRelayPorts(
+    const PortConfiguration* config,
+    const talk_base::ProxyInfo& proxy_info) {
+  PortConfiguration::RelayList::const_iterator relay;
+  std::vector<talk_base::Network*> networks;
+  network_manager_->GetNetworks(&networks);
+  if (networks.empty()) {
+    LOG(LS_ERROR) << "Machine has no networks; no relay ports created.";
+    return;
+  }
+  for (relay = config->relays.begin();
+       relay != config->relays.end(); ++relay) {
+    for (uint32 i = 0; i < networks.size(); ++i) {
+      NicMap::iterator iter = nics_.find(NicId(networks[i]->ip(),
+                                               proxy_info.address));
+      if (iter != nics_.end()) {
+        // TODO: Now setting the same start time for all protocols.
+        // This might affect accuracy, but since we are mainly looking for
+        // connect failures or number that stick out, this is good enough.
+        uint32 now = talk_base::Time();
+        NicInfo* nic_info = &iter->second;
+        nic_info->udp.start_time_ms = now;
+        nic_info->tcp.start_time_ms = now;
+        nic_info->ssltcp.start_time_ms = now;
+
+        // Add the addresses of this protocol.
+        PortConfiguration::PortList::const_iterator relay_port;
+        for (relay_port = relay->ports.begin();
+             relay_port != relay->ports.end();
+             ++relay_port) {
+          RelayPort* port = CreateRelayPort(config, networks[i]);
+          port->AddServerAddress(*relay_port);
+          port->AddExternalAddress(*relay_port);
+
+          nic_info->media_server_address = port->ServerAddress(0)->address;
+
+          // Listen to network events.
+          port->SignalAddressReady.connect(
+              this, &ConnectivityChecker::OnRelayAddressReady);
+          port->SignalAddressError.connect(
+              this, &ConnectivityChecker::OnRelayAddressError);
+
+          port->set_proxy(user_agent_, proxy_info);
+
+          // Start fetching an address for this port.
+          port->PrepareAddress();
+          ports_.push_back(port);
+        }
+      } else {
+        LOG(LS_ERROR) << "Failed to find nic info when creating relay ports.";
+      }
+    }
+  }
+}
+
+void ConnectivityChecker::AllocatePorts() {
+  PortConfiguration config(stun_address_,
+                           talk_base::CreateRandomString(16),
+                           talk_base::CreateRandomString(16),
+                           "");
+  std::vector<talk_base::Network*> networks;
+  network_manager_->GetNetworks(&networks);
+  if (networks.empty()) {
+    LOG(LS_ERROR) << "Machine has no networks; no ports will be allocated";
+    return;
+  }
+  talk_base::ProxyInfo proxy_info = GetProxyInfo();
+  bool allocate_relay_ports = false;
+  for (uint32 i = 0; i < networks.size(); ++i) {
+    if (AddNic(networks[i]->ip(), proxy_info.address)) {
+      Port* port = CreateStunPort(&config, networks[i]);
+
+      // Listen to network events.
+      port->SignalAddressReady.connect(
+          this, &ConnectivityChecker::OnStunAddressReady);
+      port->SignalAddressError.connect(
+          this, &ConnectivityChecker::OnStunAddressError);
+
+      port->set_proxy(user_agent_, proxy_info);
+      port->PrepareAddress();
+      ports_.push_back(port);
+      allocate_relay_ports = true;
+    }
+  }
+
+  // If any new ip/proxy combinations were added, send a relay allocate.
+  if (allocate_relay_ports) {
+    AllocateRelayPorts();
+  }
+
+  // Initiate proxy detection.
+  InitiateProxyDetection();
+}
+
+void ConnectivityChecker::InitiateProxyDetection() {
+  // Only start if we haven't been started before.
+  if (!proxy_detect_) {
+    proxy_detect_ = new talk_base::AutoDetectProxy(user_agent_);
+    talk_base::Url<char> host_url("/", "relay.google.com",
+                                  talk_base::HTTP_DEFAULT_PORT);
+    host_url.set_secure(true);
+    proxy_detect_->set_server_url(host_url.url());
+    proxy_detect_->SignalWorkDone.connect(
+        this, &ConnectivityChecker::OnProxyDetect);
+    proxy_detect_->Start();
+  }
+}
+
+void ConnectivityChecker::AllocateRelayPorts() {
+  // Currently we are using the 'default' nic for http(s) requests.
+  TestHttpPortAllocatorSession* allocator_session =
+      reinterpret_cast<TestHttpPortAllocatorSession*>(
+          port_allocator_->CreateSession(kSessionNameRtp, kSessionTypeVideo));
+  allocator_session->set_proxy(port_allocator_->proxy());
+  allocator_session->SignalConfigReady.connect(
+      this, &ConnectivityChecker::OnConfigReady);
+  allocator_session->SignalRequestDone.connect(
+      this, &ConnectivityChecker::OnRequestDone);
+
+  // Try both http and https.
+  RegisterHttpStart(talk_base::HTTP_SECURE_PORT);
+  allocator_session->SendSessionRequest("relay.l.google.com",
+                                        talk_base::HTTP_SECURE_PORT);
+  RegisterHttpStart(talk_base::HTTP_DEFAULT_PORT);
+  allocator_session->SendSessionRequest("relay.l.google.com",
+                                        talk_base::HTTP_DEFAULT_PORT);
+
+  sessions_.push_back(allocator_session);
+}
+
+void ConnectivityChecker::RegisterHttpStart(int port) {
+  // Since we don't know what nic were actually used for the http request,
+  // for now, just use the first one.
+  std::vector<talk_base::Network*> networks;
+  network_manager_->GetNetworks(&networks);
+  if (networks.empty()) {
+    LOG(LS_ERROR) << "No networks while registering http start.";
+    return;
+  }
+  talk_base::ProxyInfo proxy_info = GetProxyInfo();
+  NicMap::iterator i = nics_.find(NicId(networks[0]->ip(), proxy_info.address));
+  if (i != nics_.end()) {
+    uint32 now = talk_base::Time();
+    NicInfo* nic_info = &i->second;
+    if (port == talk_base::HTTP_DEFAULT_PORT) {
+      nic_info->http.start_time_ms = now;
+    } else if (port == talk_base::HTTP_SECURE_PORT) {
+      nic_info->https.start_time_ms = now;
+    } else {
+      LOG(LS_ERROR) << "Registering start time for unknown port: " << port;
+    }
+  } else {
+    LOG(LS_ERROR) << "Error, no nic info found while registering http start.";
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/p2p/client/connectivitychecker.h b/talk/p2p/client/connectivitychecker.h
new file mode 100644
index 0000000..18a1c7f
--- /dev/null
+++ b/talk/p2p/client/connectivitychecker.h
@@ -0,0 +1,260 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+
+#ifndef TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_
+#define TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_
+
+#include <map>
+#include <string>
+
+#include "talk/base/network.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/client/httpportallocator.h"
+
+namespace talk_base {
+class AsyncHttpRequest;
+class AutoDetectProxy;
+class BasicPacketSocketFactory;
+class NetworkManager;
+class PacketSocketFactory;
+class SignalThread;
+class TestHttpPortAllocatorSession;
+class Thread;
+}
+
+namespace cricket {
+class HttpPortAllocator;
+class Port;
+class PortAllocatorSession;
+struct PortConfiguration;
+class RelayPort;
+class StunPort;
+
+// Contains details about a discovered firewall that are of interest
+// when debugging call failures.
+struct FirewallInfo {
+  std::string brand;
+  std::string model;
+
+  // TODO: List of current port mappings.
+};
+
+// Contains details about a specific connect attempt.
+struct ConnectInfo {
+  ConnectInfo()
+      : rtt(-1), error(0) {}
+  // Time when the connection was initiated. Needed for calculating
+  // the round trip time.
+  uint32 start_time_ms;
+  // Round trip time in milliseconds or -1 for failed connection.
+  int32 rtt;
+  // Error code representing low level errors like socket errors.
+  int error;
+};
+
+// Identifier for a network interface and proxy address pair.
+struct NicId {
+  NicId(const talk_base::IPAddress& ip,
+        const talk_base::SocketAddress& proxy_address)
+      : ip(ip),
+        proxy_address(proxy_address) {
+  }
+  talk_base::IPAddress ip;
+  talk_base::SocketAddress proxy_address;
+};
+
+// Comparator implementation identifying unique network interface and
+// proxy address pairs.
+class NicIdComparator {
+ public:
+  int compare(const NicId &first, const NicId &second) const {
+    if (first.ip == second.ip) {
+      // Compare proxy address.
+      if (first.proxy_address == second.proxy_address) {
+        return 0;
+      } else {
+        return first.proxy_address < second.proxy_address? -1 : 1;
+      }
+    }
+    return first.ip < second.ip ? -1 : 1;
+  }
+
+  bool operator()(const NicId &first, const NicId &second) const {
+    return (compare(first, second) < 0);
+  }
+};
+
+// Contains information of a network interface and proxy address pair.
+struct NicInfo {
+  NicInfo() {}
+  talk_base::IPAddress ip;
+  talk_base::ProxyInfo proxy_info;
+  talk_base::SocketAddress external_address;
+  talk_base::SocketAddress stun_server_address;
+  talk_base::SocketAddress media_server_address;
+  ConnectInfo stun;
+  ConnectInfo http;
+  ConnectInfo https;
+  ConnectInfo udp;
+  ConnectInfo tcp;
+  ConnectInfo ssltcp;
+  FirewallInfo firewall;
+};
+
+// Holds the result of the connectivity check.
+class NicMap : public std::map<NicId, NicInfo, NicIdComparator> {
+};
+
+class TestHttpPortAllocatorSession : public HttpPortAllocatorSession {
+ public:
+  TestHttpPortAllocatorSession(
+      HttpPortAllocator* allocator, const std::string &name,
+      const std::string& session_type,
+      const std::vector<talk_base::SocketAddress>& stun_hosts,
+      const std::vector<std::string>& relay_hosts,
+      const std::string& relay_token,
+      const std::string& user_agent)
+      : HttpPortAllocatorSession(allocator, name, session_type, stun_hosts,
+                                 relay_hosts, relay_token, user_agent) {
+  }
+  void set_proxy(const talk_base::ProxyInfo& proxy) {
+    proxy_ = proxy;
+  }
+
+  void ConfigReady(PortConfiguration* config);
+
+  void OnRequestDone(talk_base::SignalThread* data);
+
+  sigslot::signal2<const PortConfiguration*,
+                   const talk_base::ProxyInfo&> SignalConfigReady;
+  sigslot::signal1<talk_base::AsyncHttpRequest*> SignalRequestDone;
+
+ private:
+  talk_base::ProxyInfo proxy_;
+};
+
+// Runs a request/response check on all network interface and proxy
+// address combinations. The check is considered done either when all
+// checks has been successful or when the check times out.
+class ConnectivityChecker
+    : public talk_base::MessageHandler, public sigslot::has_slots<> {
+ public:
+  ConnectivityChecker(talk_base::Thread* worker,
+                      const std::string& jid,
+                      const std::string& session_id,
+                      const std::string& user_agent,
+                      const std::string& relay_token,
+                      const std::string& connection);
+  virtual ~ConnectivityChecker();
+
+  // Virtual for gMock.
+  virtual bool Initialize();
+  virtual void Start();
+  virtual void Stop();
+
+  // MessageHandler implementation.
+  virtual void OnMessage(talk_base::Message *msg);
+
+  const NicMap& GetResults() const {
+    return nics_;
+  }
+
+  void set_timeout_ms(uint32 timeout) {
+    timeout_ms_ = timeout;
+  }
+
+  void set_stun_address(const talk_base::SocketAddress& stun_address) {
+    stun_address_ = stun_address;
+  }
+
+  const std::string& connection() const {
+    return connection_;
+  }
+
+  talk_base::Thread* worker() {
+    return worker_;
+  }
+
+  const std::string& jid() const {
+    return jid_;
+  }
+
+  const std::string& session_id() const {
+    return session_id_;
+  }
+
+  // Context: Main Thread. Signalled when the connectivity check is complete.
+  sigslot::signal1<ConnectivityChecker*> SignalCheckDone;
+
+ protected:
+  // Can be overridden for test.
+  virtual talk_base::NetworkManager* CreateNetworkManager() {
+    return new talk_base::BasicNetworkManager();
+  }
+  virtual talk_base::BasicPacketSocketFactory* CreateSocketFactory(
+      talk_base::Thread* thread) {
+    return new talk_base::BasicPacketSocketFactory(thread);
+  }
+  virtual HttpPortAllocator* CreatePortAllocator(
+      talk_base::NetworkManager* network_manager,
+      const std::string& user_agent,
+      const std::string& relay_token);
+  virtual StunPort* CreateStunPort(const PortConfiguration* config,
+                                   talk_base::Network* network);
+  virtual RelayPort* CreateRelayPort(const PortConfiguration* config,
+                                     talk_base::Network* network);
+  virtual void InitiateProxyDetection();
+  virtual void SetProxyInfo(const talk_base::ProxyInfo& info);
+  virtual talk_base::ProxyInfo GetProxyInfo() const;
+
+ private:
+  bool AddNic(const talk_base::IPAddress& ip,
+              const talk_base::SocketAddress& proxy_address);
+  void AllocatePorts();
+  void AllocateRelayPorts();
+  void CheckNetworks();
+  void CreateRelayPorts(const PortConfiguration* config,
+                        const talk_base::ProxyInfo& proxy_info);
+
+  // Must be called by the worker thread.
+  void CleanUp();
+
+  void OnCheckDone(bool signal_results);
+  void OnRequestDone(talk_base::AsyncHttpRequest* request);
+  void OnRelayAddressReady(Port* port);
+  void OnStunAddressReady(Port* port);
+  void OnRelayAddressError(Port* port);
+  void OnStunAddressError(Port* port);
+  void OnNetworksChanged();
+  void OnProxyDetect(talk_base::SignalThread* thread);
+  void OnConfigReady(const PortConfiguration*,
+                     const talk_base::ProxyInfo& proxy);
+  void OnConfigWithProxyReady(const PortConfiguration*);
+  void RegisterHttpStart(int port);
+  talk_base::Thread* worker_;
+  std::string jid_;
+  std::string session_id_;
+  std::string user_agent_;
+  std::string relay_token_;
+  std::string connection_;
+  talk_base::AutoDetectProxy* proxy_detect_;
+  talk_base::scoped_ptr<talk_base::NetworkManager> network_manager_;
+  talk_base::scoped_ptr<talk_base::BasicPacketSocketFactory> socket_factory_;
+  talk_base::scoped_ptr<HttpPortAllocator> port_allocator_;
+  NicMap nics_;
+  std::vector<Port*> ports_;
+  std::vector<PortAllocatorSession*> sessions_;
+  uint32 timeout_ms_;
+  talk_base::SocketAddress stun_address_;
+  talk_base::Thread* main_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_
diff --git a/talk/p2p/client/connectivitychecker_unittest.cc b/talk/p2p/client/connectivitychecker_unittest.cc
new file mode 100644
index 0000000..d63704a
--- /dev/null
+++ b/talk/p2p/client/connectivitychecker_unittest.cc
@@ -0,0 +1,367 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+
+#include <string>
+
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/gunit.h"
+#include "talk/base/fakenetwork.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/client/connectivitychecker.h"
+#include "talk/p2p/client/httpportallocator.h"
+
+namespace cricket {
+
+static const talk_base::SocketAddress kClientAddr1("11.11.11.11", 0);
+static const talk_base::SocketAddress kClientAddr2("22.22.22.22", 0);
+static const talk_base::SocketAddress kExternalAddr("33.33.33.33", 3333);
+static const talk_base::SocketAddress kStunAddr("44.44.44.44", 4444);
+static const talk_base::SocketAddress kRelayAddr("55.55.55.55", 5555);
+static const talk_base::SocketAddress kProxyAddr("66.66.66.66", 6666);
+static const talk_base::ProxyType kProxyType = talk_base::PROXY_HTTPS;
+static const char kSessionName[] = "rtp_test";
+static const char kSessionType[] = "http://www.google.com/session/test";
+static const char kRelayHost[] = "relay.google.com";
+static const char kRelayToken[] =
+    "CAESFwoOb2phQGdvb2dsZS5jb20Q043h47MmGhBTB1rbfIXkhuarDCZe+xF6";
+static const char kBrowserAgent[] = "browser_test";
+static const char kJid[] = "a.b@c";
+static const char kUserName[] = "testuser";
+static const char kPassword[] = "testpassword";
+static const char kMagicCookie[] = "testcookie";
+static const char kRelayUdpPort[] = "4444";
+static const char kRelayTcpPort[] = "5555";
+static const char kRelaySsltcpPort[] = "6666";
+static const char kSessionId[] = "testsession";
+static const char kConnection[] = "testconnection";
+static const int kMinPort = 1000;
+static const int kMaxPort = 2000;
+
+// Fake implementation to mock away real network usage.
+class FakeRelayPort : public RelayPort {
+ public:
+  FakeRelayPort(talk_base::Thread* thread,
+                talk_base::PacketSocketFactory* factory,
+                talk_base::Network* network,
+                const talk_base::IPAddress& ip,
+                int min_port,
+                int max_port,
+                const std::string& username,
+                const std::string& password,
+                const std::string& magic_cookie)
+      : RelayPort(thread,
+                  factory,
+                  network,
+                  ip,
+                  min_port,
+                  max_port,
+                  username,
+                  password,
+                  magic_cookie) {
+  }
+
+  // Just signal that we are done.
+  virtual void PrepareAddress() {
+    SignalAddressReady(this);
+  }
+};
+
+// Fake implementation to mock away real network usage.
+class FakeStunPort : public StunPort {
+ public:
+  FakeStunPort(talk_base::Thread* thread,
+               talk_base::PacketSocketFactory* factory,
+               talk_base::Network* network,
+               const talk_base::IPAddress& ip,
+               int min_port,
+               int max_port,
+               const talk_base::SocketAddress& server_addr)
+      : StunPort(thread,
+                 factory,
+                 network,
+                 ip,
+                 min_port,
+                 max_port,
+                 server_addr) {
+  }
+
+  // Just set external address and signal that we are done.
+  virtual void PrepareAddress() {
+    AddAddress(kExternalAddr, "udp", true);
+    SignalAddressReady(this);
+  }
+};
+
+// Fake implementation to mock away real network usage by responding
+// to http requests immediately.
+class FakeHttpPortAllocatorSession : public TestHttpPortAllocatorSession {
+ public:
+  FakeHttpPortAllocatorSession(
+      HttpPortAllocator* allocator,
+      const std::string& name,
+      const std::string& session_type,
+      const std::vector<talk_base::SocketAddress>& stun_hosts,
+      const std::vector<std::string>& relay_hosts,
+      const std::string& relay_token,
+      const std::string& agent)
+      : TestHttpPortAllocatorSession(allocator,
+                                     name,
+                                     session_type,
+                                     stun_hosts,
+                                     relay_hosts,
+                                     relay_token,
+                                     agent) {
+  }
+  virtual void SendSessionRequest(const std::string& host, int port) {
+    FakeReceiveSessionResponse(host, port);
+  }
+
+  // Pass results to the real implementation.
+  void FakeReceiveSessionResponse(const std::string& host, int port) {
+    talk_base::AsyncHttpRequest* response = CreateAsyncHttpResponse(port);
+    TestHttpPortAllocatorSession::OnRequestDone(response);
+    response->Destroy(true);
+  }
+
+ private:
+  // Helper method for creating a response to a relay session request.
+  talk_base::AsyncHttpRequest* CreateAsyncHttpResponse(int port) {
+    talk_base::AsyncHttpRequest* request =
+        new talk_base::AsyncHttpRequest(kBrowserAgent);
+    std::stringstream ss;
+    ss << "username=" << kUserName << std::endl
+       << "password=" << kPassword << std::endl
+       << "magic_cookie=" << kMagicCookie << std::endl
+       << "relay.ip=" << kRelayAddr.IPAsString() << std::endl
+       << "relay.udp_port=" << kRelayUdpPort << std::endl
+       << "relay.tcp_port=" << kRelayTcpPort << std::endl
+       << "relay.ssltcp_port=" << kRelaySsltcpPort << std::endl;
+    request->response().document.reset(
+        new talk_base::MemoryStream(ss.str().c_str()));
+    request->response().set_success();
+    request->set_port(port);
+    request->set_secure(port == talk_base::HTTP_SECURE_PORT);
+    return request;
+  }
+};
+
+// Fake implementation for creating fake http sessions.
+class FakeHttpPortAllocator : public HttpPortAllocator {
+ public:
+  FakeHttpPortAllocator(talk_base::NetworkManager* network_manager,
+                        const std::string& user_agent)
+      : HttpPortAllocator(network_manager, user_agent) {
+  }
+
+  virtual PortAllocatorSession* CreateSession(const std::string& name,
+                                              const std::string& session_type) {
+    std::vector<talk_base::SocketAddress> stun_hosts;
+    stun_hosts.push_back(kStunAddr);
+    std::vector<std::string> relay_hosts;
+    relay_hosts.push_back(kRelayHost);
+    return new FakeHttpPortAllocatorSession(this,
+                                            kSessionName,
+                                            kSessionType,
+                                            stun_hosts,
+                                            relay_hosts,
+                                            kRelayToken,
+                                            kBrowserAgent);
+  }
+};
+
+class ConnectivityCheckerForTest : public ConnectivityChecker {
+ public:
+  ConnectivityCheckerForTest(talk_base::Thread* worker,
+                             const std::string& jid,
+                             const std::string& session_id,
+                             const std::string& user_agent,
+                             const std::string& relay_token,
+                             const std::string& connection)
+      : ConnectivityChecker(worker,
+                            jid,
+                            session_id,
+                            user_agent,
+                            relay_token,
+                            connection),
+        proxy_initiated_(false) {
+  }
+
+  talk_base::FakeNetworkManager* network_manager() const {
+    return network_manager_;
+  }
+
+  FakeHttpPortAllocator* port_allocator() const {
+    return fake_port_allocator_;
+  }
+
+ protected:
+  // Overridden methods for faking a real network.
+  virtual talk_base::NetworkManager* CreateNetworkManager() {
+    network_manager_ = new talk_base::FakeNetworkManager();
+    return network_manager_;
+  }
+  virtual talk_base::BasicPacketSocketFactory* CreateSocketFactory(
+      talk_base::Thread* thread) {
+    // Create socket factory, for simplicity, let it run on the current thread.
+    socket_factory_ =
+        new talk_base::BasicPacketSocketFactory(talk_base::Thread::Current());
+    return socket_factory_;
+  }
+  virtual HttpPortAllocator* CreatePortAllocator(
+      talk_base::NetworkManager* network_manager,
+      const std::string& user_agent,
+      const std::string& relay_token) {
+    fake_port_allocator_ =
+        new FakeHttpPortAllocator(network_manager, user_agent);
+    return fake_port_allocator_;
+  }
+  virtual StunPort* CreateStunPort(const PortConfiguration* config,
+                                   talk_base::Network* network) {
+    return new FakeStunPort(worker(),
+                            socket_factory_,
+                            network,
+                            network->ip(),
+                            kMinPort,
+                            kMaxPort,
+                            config->stun_address);
+  }
+  virtual RelayPort* CreateRelayPort(const PortConfiguration* config,
+                                     talk_base::Network* network) {
+    return new FakeRelayPort(worker(),
+                             socket_factory_,
+                             network,
+                             network->ip(),
+                             kMinPort,
+                             kMaxPort,
+                             config->username,
+                             config->password,
+                             config->magic_cookie);
+  }
+  virtual void InitiateProxyDetection() {
+    if (!proxy_initiated_) {
+      proxy_initiated_ = true;
+      proxy_info_.address = kProxyAddr;
+      proxy_info_.type = kProxyType;
+      SetProxyInfo(proxy_info_);
+    }
+  }
+
+  virtual talk_base::ProxyInfo GetProxyInfo() const {
+    return proxy_info_;
+  }
+
+ private:
+  talk_base::BasicPacketSocketFactory* socket_factory_;
+  FakeHttpPortAllocator* fake_port_allocator_;
+  talk_base::FakeNetworkManager* network_manager_;
+  talk_base::ProxyInfo proxy_info_;
+  bool proxy_initiated_;
+};
+
+class ConnectivityCheckerTest : public testing::Test {
+ protected:
+  void VerifyNic(const NicInfo& info,
+                 const talk_base::SocketAddress& local_address) {
+    // Verify that the external address has been set.
+    EXPECT_EQ(kExternalAddr, info.external_address);
+
+    // Verify that the stun server address has been set.
+    EXPECT_EQ(kStunAddr, info.stun_server_address);
+
+    // Verify that the media server address has been set. Don't care
+    // about port since it is different for different protocols.
+    EXPECT_EQ(kRelayAddr.ipaddr(), info.media_server_address.ipaddr());
+
+    // Verify that local ip matches.
+    EXPECT_EQ(local_address.ipaddr(), info.ip);
+
+    // Verify that we have received responses for our
+    // pings. Unsuccessful ping has rtt value -1, successful >= 0.
+    EXPECT_GE(info.stun.rtt, 0);
+    EXPECT_GE(info.udp.rtt, 0);
+    EXPECT_GE(info.tcp.rtt, 0);
+    EXPECT_GE(info.ssltcp.rtt, 0);
+
+    // If proxy has been set, verify address and type.
+    if (!info.proxy_info.address.IsNil()) {
+      EXPECT_EQ(kProxyAddr, info.proxy_info.address);
+      EXPECT_EQ(kProxyType, info.proxy_info.type);
+    }
+  }
+};
+
+// Tests a configuration with two network interfaces. Verifies that 4
+// combinations of ip/proxy are created and that all protocols are
+// tested on each combination.
+TEST_F(ConnectivityCheckerTest, TestStart) {
+  ConnectivityCheckerForTest connectivity_checker(talk_base::Thread::Current(),
+                                                  kJid,
+                                                  kSessionId,
+                                                  kBrowserAgent,
+                                                  kRelayToken,
+                                                  kConnection);
+  connectivity_checker.Initialize();
+  connectivity_checker.set_stun_address(kStunAddr);
+  connectivity_checker.network_manager()->AddInterface(kClientAddr1);
+  connectivity_checker.network_manager()->AddInterface(kClientAddr2);
+
+  connectivity_checker.Start();
+  talk_base::Thread::Current()->ProcessMessages(1000);
+
+  NicMap nics = connectivity_checker.GetResults();
+
+  // There should be 4 nics in our map. 2 for each interface added,
+  // one with proxy set and one without.
+  EXPECT_EQ(4U, nics.size());
+
+  // First verify interfaces without proxy.
+  talk_base::SocketAddress nilAddress;
+
+  // First lookup the address of the first nic combined with no proxy.
+  NicMap::iterator i = nics.find(NicId(kClientAddr1.ipaddr(), nilAddress));
+  ASSERT(i != nics.end());
+  NicInfo info = i->second;
+  VerifyNic(info, kClientAddr1);
+
+  // Then make sure the second device has been tested without proxy.
+  i = nics.find(NicId(kClientAddr2.ipaddr(), nilAddress));
+  ASSERT(i != nics.end());
+  info = i->second;
+  VerifyNic(info, kClientAddr2);
+
+  // Now verify both interfaces with proxy.
+  i = nics.find(NicId(kClientAddr1.ipaddr(), kProxyAddr));
+  ASSERT(i != nics.end());
+  info = i->second;
+  VerifyNic(info, kClientAddr1);
+
+  i = nics.find(NicId(kClientAddr2.ipaddr(), kProxyAddr));
+  ASSERT(i != nics.end());
+  info = i->second;
+  VerifyNic(info, kClientAddr2);
+};
+
+// Tests that nothing bad happens if thera are no network interfaces
+// available to check.
+TEST_F(ConnectivityCheckerTest, TestStartNoNetwork) {
+  ConnectivityCheckerForTest connectivity_checker(talk_base::Thread::Current(),
+                                                  kJid,
+                                                  kSessionId,
+                                                  kBrowserAgent,
+                                                  kRelayToken,
+                                                  kConnection);
+  connectivity_checker.Initialize();
+  connectivity_checker.Start();
+  talk_base::Thread::Current()->ProcessMessages(1000);
+
+  NicMap nics = connectivity_checker.GetResults();
+
+  // Verify that no nics where checked.
+  EXPECT_EQ(0U, nics.size());
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/client/httpportallocator.cc b/talk/p2p/client/httpportallocator.cc
index 1a25287..e231fab 100644
--- a/talk/p2p/client/httpportallocator.cc
+++ b/talk/p2p/client/httpportallocator.cc
@@ -27,6 +27,7 @@
 
 #include "talk/p2p/client/httpportallocator.h"
 
+#include <algorithm>
 #include <map>
 
 #include "talk/base/asynchttprequest.h"
@@ -89,14 +90,13 @@
 
 namespace cricket {
 
-// HttpPortAllocator
+// HttpPortAllocatorBase
 
-const int HttpPortAllocator::kHostPort = 80;
-const int HttpPortAllocator::kNumRetries = 5;
+const int HttpPortAllocatorBase::kNumRetries = 5;
 
-const char HttpPortAllocator::kCreateSessionURL[] = "/create_session";
+const char HttpPortAllocatorBase::kCreateSessionURL[] = "/create_session";
 
-HttpPortAllocator::HttpPortAllocator(
+HttpPortAllocatorBase::HttpPortAllocatorBase(
     talk_base::NetworkManager* network_manager,
     talk_base::PacketSocketFactory* socket_factory,
     const std::string &user_agent)
@@ -106,7 +106,7 @@
       talk_base::SocketAddress("stun.l.google.com", 19302));
 }
 
-HttpPortAllocator::HttpPortAllocator(
+HttpPortAllocatorBase::HttpPortAllocatorBase(
     talk_base::NetworkManager* network_manager,
     const std::string &user_agent)
     : BasicPortAllocator(network_manager), agent_(user_agent) {
@@ -115,19 +115,13 @@
       talk_base::SocketAddress("stun.l.google.com", 19302));
 }
 
-HttpPortAllocator::~HttpPortAllocator() {
+HttpPortAllocatorBase::~HttpPortAllocatorBase() {
 }
 
-PortAllocatorSession *HttpPortAllocator::CreateSession(
-    const std::string& name, const std::string& session_type) {
-  return new HttpPortAllocatorSession(this, name, session_type, stun_hosts_,
-      relay_hosts_, relay_token_, agent_);
-}
+// HttpPortAllocatorSessionBase
 
-// HttpPortAllocatorSession
-
-HttpPortAllocatorSession::HttpPortAllocatorSession(
-    HttpPortAllocator* allocator, const std::string &name,
+HttpPortAllocatorSessionBase::HttpPortAllocatorSessionBase(
+    HttpPortAllocatorBase* allocator, const std::string &name,
     const std::string& session_type,
     const std::vector<talk_base::SocketAddress>& stun_hosts,
     const std::vector<std::string>& relay_hosts,
@@ -138,7 +132,9 @@
       relay_token_(relay_token), agent_(user_agent), attempts_(0) {
 }
 
-void HttpPortAllocatorSession::GetPortConfigurations() {
+HttpPortAllocatorSessionBase::~HttpPortAllocatorSessionBase() {}
+
+void HttpPortAllocatorSessionBase::GetPortConfigurations() {
   // Creating relay sessions can take time and is done asynchronously.
   // Creating stun sessions could also take time and could be done aysnc also,
   // but for now is done here and added to the initial config.  Note any later
@@ -149,7 +145,7 @@
   TryCreateRelaySession();
 }
 
-void HttpPortAllocatorSession::TryCreateRelaySession() {
+void HttpPortAllocatorSessionBase::TryCreateRelaySession() {
   if (allocator()->flags() & PORTALLOCATOR_DISABLE_RELAY) {
     LOG(LS_VERBOSE) << "HttpPortAllocator: Relay ports disabled, skipping.";
     return;
@@ -174,52 +170,10 @@
     LOG(LS_WARNING) << "No relay auth token found.";
   }
 
-  SendSessionRequest(host, HttpPortAllocator::kHostPort);
+  SendSessionRequest(host, talk_base::HTTP_SECURE_PORT);
 }
 
-void HttpPortAllocatorSession::SendSessionRequest(const std::string& host,
-                                                  int port) {
-  // Initiate an HTTP request to create a session through the chosen host.
-  talk_base::AsyncHttpRequest* request =
-      new talk_base::AsyncHttpRequest(agent_);
-  request->SignalWorkDone.connect(this,
-      &HttpPortAllocatorSession::OnRequestDone);
-
-  request->set_proxy(allocator()->proxy());
-  request->response().document.reset(new talk_base::MemoryStream);
-  request->request().verb = talk_base::HV_GET;
-  request->request().path = HttpPortAllocator::kCreateSessionURL;
-  request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token_, true);
-  request->request().addHeader("X-Google-Relay-Auth", relay_token_, true);
-  request->request().addHeader("X-Session-Type", session_type(), true);
-  request->request().addHeader("X-Stream-Type", name(), true);
-  request->set_host(host);
-  request->set_port(port);
-  request->Start();
-  request->Release();
-}
-
-void HttpPortAllocatorSession::OnRequestDone(talk_base::SignalThread* data) {
-  talk_base::AsyncHttpRequest* request =
-      static_cast<talk_base::AsyncHttpRequest*>(data);
-  if (request->response().scode != 200) {
-    LOG(LS_WARNING) << "HTTPPortAllocator: request "
-                    << " received error " << request->response().scode;
-    TryCreateRelaySession();
-    return;
-  }
-  LOG(LS_INFO) << "HTTPPortAllocator: request succeeded";
-
-  talk_base::MemoryStream* stream =
-      static_cast<talk_base::MemoryStream*>(request->response().document.get());
-  stream->Rewind();
-  size_t length;
-  stream->GetSize(&length);
-  std::string resp = std::string(stream->GetBuffer(), length);
-  ReceiveSessionResponse(resp);
-}
-
-void HttpPortAllocatorSession::ReceiveSessionResponse(
+void HttpPortAllocatorSessionBase::ReceiveSessionResponse(
     const std::string& response) {
 
   StringMap map;
@@ -256,4 +210,99 @@
   ConfigReady(config);
 }
 
+// HttpPortAllocator
+
+HttpPortAllocator::HttpPortAllocator(
+    talk_base::NetworkManager* network_manager,
+    talk_base::PacketSocketFactory* socket_factory,
+    const std::string &user_agent)
+    : HttpPortAllocatorBase(network_manager, socket_factory, user_agent) {
+}
+
+HttpPortAllocator::HttpPortAllocator(
+    talk_base::NetworkManager* network_manager,
+    const std::string &user_agent)
+    : HttpPortAllocatorBase(network_manager, user_agent) {
+}
+HttpPortAllocator::~HttpPortAllocator() {}
+
+PortAllocatorSession* HttpPortAllocator::CreateSession(
+    const std::string& name, const std::string& session_type) {
+  return new HttpPortAllocatorSession(this, name, session_type, stun_hosts(),
+      relay_hosts(), relay_token(), user_agent());
+}
+
+// HttpPortAllocatorSession
+
+HttpPortAllocatorSession::HttpPortAllocatorSession(
+    HttpPortAllocator* allocator,
+    const std::string& name,
+    const std::string& session_type,
+    const std::vector<talk_base::SocketAddress>& stun_hosts,
+    const std::vector<std::string>& relay_hosts,
+    const std::string& relay,
+    const std::string& agent)
+    : HttpPortAllocatorSessionBase(allocator, name, session_type, stun_hosts,
+                                   relay_hosts, relay, agent) {
+}
+
+HttpPortAllocatorSession::~HttpPortAllocatorSession() {
+  for (std::list<talk_base::AsyncHttpRequest*>::iterator it = requests_.begin();
+       it != requests_.end(); ++it) {
+    (*it)->Destroy(true);
+  }
+}
+
+void HttpPortAllocatorSession::SendSessionRequest(const std::string& host,
+                                                  int port) {
+  // Initiate an HTTP request to create a session through the chosen host.
+  talk_base::AsyncHttpRequest* request =
+      new talk_base::AsyncHttpRequest(user_agent());
+  request->SignalWorkDone.connect(this,
+      &HttpPortAllocatorSession::OnRequestDone);
+
+  request->set_secure(port == talk_base::HTTP_SECURE_PORT);
+  request->set_proxy(allocator()->proxy());
+  request->response().document.reset(new talk_base::MemoryStream);
+  request->request().verb = talk_base::HV_GET;
+  request->request().path = HttpPortAllocator::kCreateSessionURL;
+  request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token(), true);
+  request->request().addHeader("X-Google-Relay-Auth", relay_token(), true);
+  request->request().addHeader("X-Session-Type", session_type(), true);
+  request->request().addHeader("X-Stream-Type", name(), true);
+  request->set_host(host);
+  request->set_port(port);
+  request->Start();
+  request->Release();
+  requests_.push_back(request);
+}
+
+void HttpPortAllocatorSession::OnRequestDone(talk_base::SignalThread* data) {
+  talk_base::AsyncHttpRequest* request =
+      static_cast<talk_base::AsyncHttpRequest*>(data);
+
+  // Remove the request from the list of active requests.
+  std::list<talk_base::AsyncHttpRequest*>::iterator it =
+      std::find(requests_.begin(), requests_.end(), request);
+  if (it != requests_.end()) {
+    requests_.erase(it);
+  }
+
+  if (request->response().scode != 200) {
+    LOG(LS_WARNING) << "HTTPPortAllocator: request "
+                    << " received error " << request->response().scode;
+    TryCreateRelaySession();
+    return;
+  }
+  LOG(LS_INFO) << "HTTPPortAllocator: request succeeded";
+
+  talk_base::MemoryStream* stream =
+      static_cast<talk_base::MemoryStream*>(request->response().document.get());
+  stream->Rewind();
+  size_t length;
+  stream->GetSize(&length);
+  std::string resp = std::string(stream->GetBuffer(), length);
+  ReceiveSessionResponse(resp);
+}
+
 }  // namespace cricket
diff --git a/talk/p2p/client/httpportallocator.h b/talk/p2p/client/httpportallocator.h
index e7211c5..e47a69e 100644
--- a/talk/p2p/client/httpportallocator.h
+++ b/talk/p2p/client/httpportallocator.h
@@ -28,36 +28,39 @@
 #ifndef TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
 #define TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
 
+#include <list>
 #include <string>
 #include <vector>
 #include "talk/p2p/client/basicportallocator.h"
 
 namespace talk_base {
+class AsyncHttpRequest;
 class SignalThread;
 }
 
 namespace cricket {
 
-class HttpPortAllocator : public BasicPortAllocator {
+class HttpPortAllocatorBase : public BasicPortAllocator {
  public:
-  // Records the port on the hosts that will receive HTTP requests.
-  static const int kHostPort;
-
   // The number of HTTP requests we should attempt before giving up.
   static const int kNumRetries;
 
   // Records the URL that we will GET in order to create a session.
   static const char kCreateSessionURL[];
 
-  HttpPortAllocator(talk_base::NetworkManager* network_manager,
-                    const std::string& user_agent);
-  HttpPortAllocator(talk_base::NetworkManager* network_manager,
-                    talk_base::PacketSocketFactory* socket_factory,
-                    const std::string& user_agent);
-  virtual ~HttpPortAllocator();
+  HttpPortAllocatorBase(talk_base::NetworkManager* network_manager,
+                        const std::string& user_agent);
+  HttpPortAllocatorBase(talk_base::NetworkManager* network_manager,
+                        talk_base::PacketSocketFactory* socket_factory,
+                        const std::string& user_agent);
+  virtual ~HttpPortAllocatorBase();
 
-  virtual PortAllocatorSession* CreateSession(const std::string& name,
-                                              const std::string& session_type);
+  // CreateSession is defined in BasicPortAllocator but is
+  // redefined here as pure virtual.
+  virtual PortAllocatorSession* CreateSession(
+      const std::string& name,
+      const std::string& session_type) = 0;
+
   void SetStunHosts(const std::vector<talk_base::SocketAddress>& hosts) {
     if (!hosts.empty()) {
       stun_hosts_ = hosts;
@@ -95,7 +98,58 @@
 
 class RequestData;
 
-class HttpPortAllocatorSession : public BasicPortAllocatorSession {
+class HttpPortAllocatorSessionBase : public BasicPortAllocatorSession {
+ public:
+  HttpPortAllocatorSessionBase(
+      HttpPortAllocatorBase* allocator,
+      const std::string& name,
+      const std::string& session_type,
+      const std::vector<talk_base::SocketAddress>& stun_hosts,
+      const std::vector<std::string>& relay_hosts,
+      const std::string& relay,
+      const std::string& agent);
+  virtual ~HttpPortAllocatorSessionBase();
+
+  const std::string& relay_token() const {
+    return relay_token_;
+  }
+
+  const std::string& user_agent() const {
+      return agent_;
+  }
+
+  virtual void SendSessionRequest(const std::string& host, int port) = 0;
+  virtual void ReceiveSessionResponse(const std::string& response);
+
+ protected:
+  virtual void GetPortConfigurations();
+  void TryCreateRelaySession();
+  virtual HttpPortAllocatorBase* allocator() {
+    return static_cast<HttpPortAllocatorBase*>(
+        BasicPortAllocatorSession::allocator());
+  }
+
+ private:
+  std::vector<std::string> relay_hosts_;
+  std::vector<talk_base::SocketAddress> stun_hosts_;
+  std::string relay_token_;
+  std::string agent_;
+  int attempts_;
+};
+
+class HttpPortAllocator : public HttpPortAllocatorBase {
+ public:
+  HttpPortAllocator(talk_base::NetworkManager* network_manager,
+                    const std::string& user_agent);
+  HttpPortAllocator(talk_base::NetworkManager* network_manager,
+                    talk_base::PacketSocketFactory* socket_factory,
+                    const std::string& user_agent);
+  virtual ~HttpPortAllocator();
+  virtual PortAllocatorSession* CreateSession(const std::string& name,
+                                              const std::string& session_type);
+};
+
+class HttpPortAllocatorSession : public HttpPortAllocatorSessionBase {
  public:
   HttpPortAllocatorSession(
       HttpPortAllocator* allocator,
@@ -105,31 +159,16 @@
       const std::vector<std::string>& relay_hosts,
       const std::string& relay,
       const std::string& agent);
-  virtual ~HttpPortAllocatorSession() {}
+  virtual ~HttpPortAllocatorSession();
 
-  const std::string& relay_token() const {
-    return relay_token_;
-  }
   virtual void SendSessionRequest(const std::string& host, int port);
-  virtual void ReceiveSessionResponse(const std::string& response);
 
  protected:
-  virtual void GetPortConfigurations();
-  void TryCreateRelaySession();
+  // Protected for diagnostics.
+  virtual void OnRequestDone(talk_base::SignalThread* request);
 
  private:
-  virtual HttpPortAllocator* allocator() {
-    return static_cast<HttpPortAllocator*>(
-        BasicPortAllocatorSession::allocator());
-  }
-
-  void OnRequestDone(talk_base::SignalThread* request);
-
-  std::vector<std::string> relay_hosts_;
-  std::vector<talk_base::SocketAddress> stun_hosts_;
-  std::string relay_token_;
-  std::string agent_;
-  int attempts_;
+  std::list<talk_base::AsyncHttpRequest*> requests_;
 };
 
 }  // namespace cricket
diff --git a/talk/p2p/client/portallocator_unittest.cc b/talk/p2p/client/portallocator_unittest.cc
new file mode 100644
index 0000000..f5bb37c
--- /dev/null
+++ b/talk/p2p/client/portallocator_unittest.cc
@@ -0,0 +1,396 @@
+/*
+ * libjingle
+ * Copyright 2009 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/fakenetwork.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/network.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+#include "talk/p2p/base/portallocatorsessionproxy.h"
+#include "talk/p2p/base/testrelayserver.h"
+#include "talk/p2p/base/teststunserver.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/p2p/client/httpportallocator.h"
+
+using talk_base::SocketAddress;
+using talk_base::Thread;
+
+static const SocketAddress kClientAddr("11.11.11.11", 0);
+static const SocketAddress kRemoteClientAddr("22.22.22.22", 0);
+static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
+static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000);
+static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
+static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002);
+static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003);
+static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004);
+static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005);
+
+// Minimum and maximum port for port range tests.
+static const int kMinPort = 10000;
+static const int kMaxPort = 10099;
+
+// Helper for dumping candidates
+std::ostream& operator<<(std::ostream& os, const cricket::Candidate& c) {
+  os << c.ToString();
+  return os;
+}
+
+class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+  static void SetUpTestCase() {
+    // Ensure the RNG is inited.
+    talk_base::InitRandom(NULL, 0);
+  }
+  PortAllocatorTest()
+      : pss_(new talk_base::PhysicalSocketServer),
+        vss_(new talk_base::VirtualSocketServer(pss_.get())),
+        fss_(new talk_base::FirewallSocketServer(vss_.get())),
+        ss_scope_(fss_.get()),
+        stun_server_(Thread::Current(), kStunAddr),
+        relay_server_(Thread::Current(), kRelayUdpIntAddr, kRelayUdpExtAddr,
+                      kRelayTcpIntAddr, kRelayTcpExtAddr,
+                      kRelaySslTcpIntAddr, kRelaySslTcpExtAddr),
+        allocator_(&network_manager_, kStunAddr,
+                   kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr) {
+  }
+
+  void AddInterface(const SocketAddress& addr) {
+    network_manager_.AddInterface(addr);
+  }
+  bool SetPortRange(int min_port, int max_port) {
+    return allocator_.SetPortRange(min_port, max_port);
+  }
+  bool CreateSession(const std::string& name, const std::string& type) {
+    session_.reset(allocator_.CreateSession(name, type));
+    if (!session_.get())
+      return false;
+
+    session_->SignalPortReady.connect(this,
+        &PortAllocatorTest::OnPortReady);
+    session_->SignalCandidatesReady.connect(this,
+        &PortAllocatorTest::OnCandidatesReady);
+    return true;
+  }
+
+  cricket::PortAllocatorSession* CreateSession(
+      const std::string& sid, const std::string& name,
+      const std::string& type, cricket::PortAllocator* allocator) {
+    cricket::PortAllocatorSession* session =
+        allocator->CreateSession(sid, name, type);
+    session->SignalPortReady.connect(this,
+            &PortAllocatorTest::OnPortReady);
+    session->SignalCandidatesReady.connect(this,
+        &PortAllocatorTest::OnCandidatesReady);
+    return session;
+  }
+
+  static bool CheckCandidate(const cricket::Candidate& c,
+                             const std::string& name, const std::string& type,
+                             const std::string& proto,
+                             const SocketAddress& addr) {
+    return (c.name() == name && c.type() == type &&
+        c.protocol() == proto && c.address().ipaddr() == addr.ipaddr() &&
+        (addr.port() == 0 || (c.address().port() == addr.port())));
+  }
+  static bool CheckPort(const talk_base::SocketAddress& addr,
+                        int min_port, int max_port) {
+    return (addr.port() >= min_port && addr.port() <= max_port);
+  }
+
+  cricket::PortAllocator* CreateAllocator() {
+    return new cricket::BasicPortAllocator(
+        &network_manager_, kStunAddr, SocketAddress(),
+        SocketAddress(), SocketAddress());
+  }
+
+  cricket::P2PTransportChannel* CreateTransportChannel(
+      const std::string& name, cricket::PortAllocator* allocator) {
+    return new cricket::P2PTransportChannel(name, "unittest", NULL, allocator);
+  }
+
+ protected:
+  cricket::BasicPortAllocator& allocator() { return allocator_; }
+
+  void OnPortReady(cricket::PortAllocatorSession* ses, cricket::Port* port) {
+    LOG(LS_INFO) << "OnPortReady: " << port->ToString();
+    ports_.push_back(port);
+  }
+  void OnCandidatesReady(cricket::PortAllocatorSession* ses,
+                         const std::vector<cricket::Candidate>& candidates) {
+    for (size_t i = 0; i < candidates.size(); ++i) {
+      LOG(LS_INFO) << "OnCandidatesReady: " << candidates[i].ToString();
+      candidates_.push_back(candidates[i]);
+    }
+  }
+
+  talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
+  talk_base::scoped_ptr<talk_base::VirtualSocketServer> vss_;
+  talk_base::scoped_ptr<talk_base::FirewallSocketServer> fss_;
+  talk_base::SocketServerScope ss_scope_;
+  cricket::TestStunServer stun_server_;
+  cricket::TestRelayServer relay_server_;
+  talk_base::FakeNetworkManager network_manager_;
+  cricket::BasicPortAllocator allocator_;
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session_;
+  std::vector<cricket::Port*> ports_;
+  std::vector<cricket::Candidate> candidates_;
+};
+
+// Tests that we can init the port allocator and create a session.
+TEST_F(PortAllocatorTest, TestBasic) {
+  EXPECT_EQ(&network_manager_, allocator().network_manager());
+  EXPECT_EQ(kStunAddr, allocator().stun_address());
+  EXPECT_EQ(kRelayUdpIntAddr, allocator().relay_address_udp());
+  EXPECT_EQ(kRelayTcpIntAddr, allocator().relay_address_tcp());
+  EXPECT_EQ(kRelaySslTcpIntAddr, allocator().relay_address_ssl());
+  EXPECT_TRUE(CreateSession("rtp", "unittest"));
+}
+
+// Tests that we can get the local and STUN addresses successfully.
+TEST_F(PortAllocatorTest, TestGetInitialPorts) {
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession("rtp", "unittest"));
+  session_->GetInitialPorts();
+  ASSERT_EQ_WAIT(2U, candidates_.size(), 1000);
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      "rtp", "local", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      "rtp", "stun", "udp", kClientAddr);
+  EXPECT_EQ(2U, ports_.size());
+}
+
+// Tests that we can get all the desired addresses successfully.
+TEST_F(PortAllocatorTest, TestGetAllPorts) {
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession("rtp", "unittest"));
+  session_->GetInitialPorts();
+  session_->StartGetAllPorts();
+  ASSERT_EQ_WAIT(2U, candidates_.size(), 1000);
+  EXPECT_EQ(2U, ports_.size());
+  ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
+  EXPECT_EQ(3U, ports_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[2],
+      "rtp", "relay", "udp", kRelayUdpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[3],
+      "rtp", "relay", "udp", kRelayUdpExtAddr);
+  ASSERT_EQ_WAIT(6U, candidates_.size(), 1500);
+  EXPECT_PRED5(CheckCandidate, candidates_[4],
+      "rtp", "relay", "tcp", kRelayTcpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[5],
+      "rtp", "local", "tcp", kClientAddr);
+  EXPECT_EQ(4U, ports_.size());
+  ASSERT_EQ_WAIT(7U, candidates_.size(), 2000);
+  EXPECT_PRED5(CheckCandidate, candidates_[6],
+      "rtp", "relay", "ssltcp", kRelaySslTcpIntAddr);
+  EXPECT_EQ(4U, ports_.size());
+}
+
+// Test that we restrict client ports appropriately when a port range is set.
+// We check the candidates for udp/stun/tcp ports, and the from address
+// for relay ports.
+TEST_F(PortAllocatorTest, TestGetAllPortsPortRange) {
+  AddInterface(kClientAddr);
+  // Check that an invalid port range fails.
+  EXPECT_FALSE(SetPortRange(kMaxPort, kMinPort));
+  // Check that a null port range succeeds.
+  EXPECT_TRUE(SetPortRange(0, 0));
+  // Check that a valid port range succeeds.
+  EXPECT_TRUE(SetPortRange(kMinPort, kMaxPort));
+  EXPECT_TRUE(CreateSession("rtp", "unittest"));
+  session_->GetInitialPorts();
+  session_->StartGetAllPorts();
+  ASSERT_EQ_WAIT(2U, candidates_.size(), 1000);
+  EXPECT_EQ(2U, ports_.size());
+  // Check the port number for the UDP port object.
+  EXPECT_PRED3(CheckPort, candidates_[0].address(), kMinPort, kMaxPort);
+  // Check the port number for the STUN port object.
+  EXPECT_PRED3(CheckPort, candidates_[1].address(), kMinPort, kMaxPort);
+  ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
+  EXPECT_EQ(3U, ports_.size());
+  // Check the port number used to connect to the relay server.
+  EXPECT_PRED3(CheckPort, relay_server_.GetConnection(0).source(),
+               kMinPort, kMaxPort);
+  ASSERT_EQ_WAIT(6U, candidates_.size(), 1500);
+  EXPECT_EQ(4U, ports_.size());
+  // Check the port number for the TCP port object.
+  EXPECT_PRED3(CheckPort, candidates_[5].address(), kMinPort, kMaxPort);
+}
+
+// Test that we don't crash or malfunction if we have no network adapters.
+// TODO: Find a way to exit early here.
+TEST_F(PortAllocatorTest, TestGetAllPortsNoAdapters) {
+  EXPECT_TRUE(CreateSession("rtp", "unittest"));
+  session_->GetInitialPorts();
+  session_->StartGetAllPorts();
+  WAIT(candidates_.size() > 0, 2000);
+}
+
+// Test that we don't crash or malfunction if we can't create UDP sockets.
+TEST_F(PortAllocatorTest, TestGetAllPortsNoUdpSockets) {
+  AddInterface(kClientAddr);
+  fss_->set_udp_sockets_enabled(false);
+  EXPECT_TRUE(CreateSession("rtp", "unittest"));
+  session_->GetInitialPorts();
+  session_->StartGetAllPorts();
+  ASSERT_EQ_WAIT(2U, candidates_.size(), 2000);
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      "rtp", "relay", "udp", kRelayUdpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      "rtp", "relay", "udp", kRelayUdpExtAddr);
+  ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
+  EXPECT_PRED5(CheckCandidate, candidates_[2],
+      "rtp", "relay", "tcp", kRelayTcpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[3],
+      "rtp", "local", "tcp", kClientAddr);
+  EXPECT_EQ(2U, ports_.size());
+  ASSERT_EQ_WAIT(5U, candidates_.size(), 2000);
+  EXPECT_PRED5(CheckCandidate, candidates_[4],
+      "rtp", "relay", "ssltcp", kRelaySslTcpIntAddr);
+  EXPECT_EQ(2U, ports_.size());
+}
+
+// Test that we don't crash or malfunction if we can't create UDP sockets or
+// listen on TCP sockets. We still give out a local TCP address, since
+// apparently this is needed for the remote side to accept our connection.
+TEST_F(PortAllocatorTest, TestGetAllPortsNoUdpSocketsNoTcpListen) {
+  AddInterface(kClientAddr);
+  fss_->set_udp_sockets_enabled(false);
+  fss_->set_tcp_listen_enabled(false);
+  EXPECT_TRUE(CreateSession("rtp", "unittest"));
+  session_->GetInitialPorts();
+  session_->StartGetAllPorts();
+  ASSERT_EQ_WAIT(2U, candidates_.size(), 3000);
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      "rtp", "relay", "udp", kRelayUdpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      "rtp", "relay", "udp", kRelayUdpExtAddr);
+  ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
+  EXPECT_PRED5(CheckCandidate, candidates_[2],
+      "rtp", "relay", "tcp", kRelayTcpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[3],
+      "rtp", "local", "tcp", kClientAddr);
+  EXPECT_EQ(2U, ports_.size());
+  ASSERT_EQ_WAIT(5U, candidates_.size(), 2000);
+  EXPECT_PRED5(CheckCandidate, candidates_[4],
+      "rtp", "relay", "ssltcp", kRelaySslTcpIntAddr);
+  EXPECT_EQ(2U, ports_.size());
+}
+
+// Test that we don't crash or malfunction if we can't create any sockets.
+// TODO: Find a way to exit early here.
+TEST_F(PortAllocatorTest, TestGetAllPortsNoSockets) {
+  AddInterface(kClientAddr);
+  fss_->set_tcp_sockets_enabled(false);
+  fss_->set_udp_sockets_enabled(false);
+  EXPECT_TRUE(CreateSession("rtp", "unittest"));
+  session_->GetInitialPorts();
+  session_->StartGetAllPorts();
+  WAIT(candidates_.size() > 0, 2000);
+}
+
+// Test that the httpportallocator correctly maintains its lists of stun and
+// relay servers, by never allowing an empty list.
+TEST(HttpPortAllocatorTest, TestHttpPortAllocatorHostLists) {
+  talk_base::FakeNetworkManager network_manager;
+  cricket::HttpPortAllocator alloc(&network_manager, "unit test agent");
+  EXPECT_EQ(1U, alloc.relay_hosts().size());
+  EXPECT_EQ(1U, alloc.stun_hosts().size());
+
+  std::vector<std::string> relay_servers;
+  std::vector<talk_base::SocketAddress> stun_servers;
+
+  alloc.SetRelayHosts(relay_servers);
+  alloc.SetStunHosts(stun_servers);
+  EXPECT_EQ(1U, alloc.relay_hosts().size());
+  EXPECT_EQ(1U, alloc.stun_hosts().size());
+
+  relay_servers.push_back("1.unittest.corp.google.com");
+  relay_servers.push_back("2.unittest.corp.google.com");
+  stun_servers.push_back(
+      talk_base::SocketAddress("1.unittest.corp.google.com", 0));
+  stun_servers.push_back(
+      talk_base::SocketAddress("2.unittest.corp.google.com", 0));
+  alloc.SetRelayHosts(relay_servers);
+  alloc.SetStunHosts(stun_servers);
+  EXPECT_EQ(2U, alloc.relay_hosts().size());
+  EXPECT_EQ(2U, alloc.stun_hosts().size());
+}
+
+TEST_F(PortAllocatorTest, TestBasicMuxFeatures) {
+  talk_base::scoped_ptr<cricket::PortAllocator> allocator(CreateAllocator());
+  allocator->set_flags(cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  // Session ID - session1.
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session1(
+      CreateSession("session1", "rtp", "audio", allocator.get()));
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session2(
+      CreateSession("session1", "rtcp", "audio", allocator.get()));
+  // We know that PortAllocator is creating a proxy session when bundle flag
+  // is enabled, it's safe to type cast session objects.
+  cricket::PortAllocatorSessionProxy* proxy1 =
+      static_cast<cricket::PortAllocatorSessionProxy*>(session1.get());
+  ASSERT_TRUE(proxy1 != NULL);
+  cricket::PortAllocatorSessionProxy* proxy2 =
+      static_cast<cricket::PortAllocatorSessionProxy*>(session2.get());
+  ASSERT_TRUE(proxy2 != NULL);
+  EXPECT_EQ(proxy1->impl(), proxy2->impl());
+  AddInterface(kClientAddr);
+  session1->GetInitialPorts();
+  session2->GetInitialPorts();
+  // Each session should receive two proxy ports of local and stun.
+  ASSERT_EQ_WAIT(4U, ports_.size(), 1000);
+  EXPECT_EQ(4U, candidates_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      "rtp", "local", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      "rtcp", "local", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[2],
+      "rtp", "stun", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[3],
+      "rtcp", "stun", "udp", kClientAddr);
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session3(
+      CreateSession("session1", "video_rtp", "video", allocator.get()));
+  // ListenToEvents(session3.get());
+  session3->GetInitialPorts();
+  // Since real ports and sessions are already allocated and signal sent, no
+  // new ports will be allocated when new proxy session created.
+  talk_base::Thread::Current()->ProcessMessages(1000);
+  EXPECT_NE(6U, ports_.size());
+  // Creating a PortAllocatorSession with different session name from above.
+  // In this case proxy PAS should have a different PAS.
+  // Session ID - session2.
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session4(
+        CreateSession("session2", "video_rtp", "video", allocator.get()));
+  cricket::PortAllocatorSessionProxy* proxy4 =
+        static_cast<cricket::PortAllocatorSessionProxy*>(session4.get());
+  EXPECT_NE(proxy4->impl(), proxy1->impl());
+}
diff --git a/talk/session/phone/audioframe.h b/talk/session/phone/audioframe.h
new file mode 100644
index 0000000..bcd8410
--- /dev/null
+++ b/talk/session/phone/audioframe.h
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_AUDIOFRAME_H_
+#define TALK_SESSION_PHONE_AUDIOFRAME_H_
+
+namespace cricket {
+
+class AudioFrame {
+ public:
+  AudioFrame()
+      : audio10ms_(NULL),
+        length_(0),
+        sampling_frequency_(8000),
+        stereo_(false) {
+  }
+  AudioFrame(int16* audio, size_t audio_length, int sample_freq, bool stereo)
+      : audio10ms_(audio),
+        length_(audio_length),
+        sampling_frequency_(sample_freq),
+        stereo_(stereo) {
+  }
+
+  int16* GetData() { return audio10ms_; }
+  size_t GetSize() const { return length_; }
+  int GetSamplingFrequency() const { return sampling_frequency_; }
+  bool GetStereo() const { return stereo_; }
+
+ private:
+  int16* audio10ms_;
+  size_t length_;
+  int sampling_frequency_;
+  bool stereo_;
+};
+
+}  // namespace cricket
+#endif  // TALK_SESSION_PHONE_AUDIOFRAME_H_
diff --git a/talk/session/phone/call.cc b/talk/session/phone/call.cc
index e01e937..0273353 100644
--- a/talk/session/phone/call.cc
+++ b/talk/session/phone/call.cc
@@ -29,6 +29,7 @@
 #include "talk/base/helpers.h"
 #include "talk/base/logging.h"
 #include "talk/base/thread.h"
+#include "talk/p2p/base/parsing.h"
 #include "talk/session/phone/call.h"
 #include "talk/session/phone/mediasessionclient.h"
 
@@ -140,8 +141,8 @@
   StaticVideoViews::const_iterator it;
   for (it = view_request.static_video_views.begin();
        it != view_request.static_video_views.end(); ++it) {
-    NamedSource found_source;
-    bool found = media_sources_.GetVideoSourceBySsrc(it->ssrc, &found_source);
+    StreamParams found_stream;
+    bool found = recv_streams_.GetVideoStreamBySsrc(it->ssrc, &found_stream);
     if (!found) {
       LOG(LS_WARNING) <<
           "Tried sending view request for bad ssrc: " << it->ssrc;
@@ -151,7 +152,7 @@
 
   XmlElements elems;
   WriteError error;
-  if (!WriteViewRequest(CN_VIDEO, view_request, &elems, &error)) {
+  if (!WriteJingleViewRequest(CN_VIDEO, view_request, &elems, &error)) {
     LOG(LS_ERROR) << "Couldn't write out view request: " << error.text;
     return false;
   }
@@ -178,34 +179,41 @@
   }
 }
 
-void Call::AddVoiceStream(Session *session, uint32 voice_ssrc) {
+
+
+
+void Call::AddAudioRecvStream(Session *session, const StreamParams& stream) {
   VoiceChannel *voice_channel = GetVoiceChannel(session);
-  if (voice_channel && voice_ssrc) {
-    voice_channel->AddStream(voice_ssrc);
+  if (voice_channel && stream.has_ssrcs()) {
+    voice_channel->AddRecvStream(stream);
   }
+  recv_streams_.AddAudioStream(stream);
 }
 
-void Call::AddVideoStream(Session *session, uint32 video_ssrc) {
+void Call::AddVideoRecvStream(Session *session, const StreamParams& stream) {
   VideoChannel *video_channel = GetVideoChannel(session);
-  if (video_channel && video_ssrc) {
-    // TODO: Do we need the audio_ssrc here?
-    // It doesn't seem to be used.
-    video_channel->AddStream(video_ssrc, 0U);
+  if (video_channel && stream.has_ssrcs()) {
+    video_channel->AddRecvStream(stream);
   }
+  recv_streams_.AddVideoStream(stream);
 }
 
-void Call::RemoveVoiceStream(Session *session, uint32 voice_ssrc) {
+void Call::RemoveAudioRecvStream(Session *session, const StreamParams& stream) {
   VoiceChannel *voice_channel = GetVoiceChannel(session);
-  if (voice_channel && voice_ssrc) {
-    voice_channel->RemoveStream(voice_ssrc);
+  // TODO: Change RemoveRecvStream to take a stream argument.
+  if (voice_channel && stream.has_ssrcs()) {
+    voice_channel->RemoveRecvStream(stream.first_ssrc());
   }
+  recv_streams_.RemoveAudioStreamByNickAndName(stream.nick, stream.name);
 }
 
-void Call::RemoveVideoStream(Session *session, uint32 video_ssrc) {
+void Call::RemoveVideoRecvStream(Session *session, const StreamParams& stream) {
   VideoChannel *video_channel = GetVideoChannel(session);
-  if (video_channel && video_ssrc) {
-    video_channel->RemoveStream(video_ssrc);
+  // TODO: Change RemoveRecvStream to take a stream argument.
+  if (video_channel && stream.has_ssrcs()) {
+    video_channel->RemoveRecvStream(stream.first_ssrc());
   }
+  recv_streams_.RemoveVideoStreamByNickAndName(stream.nick, stream.name);
 }
 
 void Call::OnMessage(talk_base::Message *message) {
@@ -239,13 +247,6 @@
   VoiceChannel *voice_channel = NULL;
   VideoChannel *video_channel = NULL;
 
-  // Generate a random string for the RTCP CNAME, as stated in RFC 6222.
-  // This string is only used for synchronization, and therefore is opaque.
-  std::string rtcp_cname;
-  if (!talk_base::CreateRandomString(16, &rtcp_cname)) {
-    return false;
-  }
-
   const ContentInfo* audio_offer = GetFirstAudioContent(offer);
   const ContentInfo* video_offer = GetFirstVideoContent(offer);
   video_ = (video_offer != NULL);
@@ -257,7 +258,6 @@
   // voice_channel can be NULL in case of NullVoiceEngine.
   if (voice_channel) {
     voice_channel_map_[session->id()] = voice_channel;
-    voice_channel->SetRtcpCName(rtcp_cname);
     voice_channel->SignalMediaMonitor.connect(this, &Call::OnMediaMonitor);
     voice_channel->StartMediaMonitor(kMediaMonitorInterval);
   } else {
@@ -271,7 +271,6 @@
     // video_channel can be NULL in case of NullVideoEngine.
     if (video_channel) {
       video_channel_map_[session->id()] = video_channel;
-      video_channel->SetRtcpCName(rtcp_cname);
       video_channel->SignalMediaMonitor.connect(this, &Call::OnMediaMonitor);
       video_channel->StartMediaMonitor(kMediaMonitorInterval);
     } else {
@@ -284,7 +283,10 @@
     sessions_.push_back(session);
     session->SignalState.connect(this, &Call::OnSessionState);
     session->SignalError.connect(this, &Call::OnSessionError);
-    session->SignalInfoMessage.connect(this, &Call::OnSessionInfo);
+    session->SignalInfoMessage.connect(
+        this, &Call::OnSessionInfoMessage);
+    session->SignalRemoteDescriptionUpdate.connect(
+        this, &Call::OnRemoteDescriptionUpdate);
     session->SignalReceivedTerminateReason
       .connect(this, &Call::OnReceivedTerminateReason);
 
@@ -553,11 +555,10 @@
 }
 
 void Call::OnSpeakerMonitor(CurrentSpeakerMonitor* monitor, uint32 ssrc) {
-  NamedSource source;
-  source.ssrc = ssrc;
-  media_sources_.GetAudioSourceBySsrc(ssrc, &source);
+  StreamParams stream;
+  recv_streams_.GetAudioStreamBySsrc(ssrc, &stream);
   SignalSpeakerMonitor(this, static_cast<Session *>(monitor->session()),
-                       source);
+                       stream);
 }
 
 void Call::OnConnectionMonitor(VideoChannel *channel,
@@ -593,92 +594,136 @@
   SignalSessionError(this, static_cast<Session *>(session), error);
 }
 
-void Call::OnSessionInfo(Session *session,
-                         const buzz::XmlElement* action_elem) {
-  // We have a different list of "updates" because we only want to
-  // signal the sources that were added or removed.  We want to filter
-  // out un-changed sources.
-  cricket::MediaSources updates;
+void Call::OnSessionInfoMessage(Session *session,
+                                const buzz::XmlElement* action_elem) {
+  if (!IsJingleViewRequest(action_elem)) {
+    return;
+  }
 
-  if (IsSourcesNotify(action_elem)) {
-    MediaSources sources;
-    ParseError error;
-    if (!ParseSourcesNotify(action_elem, session->remote_description(),
-                            &sources, &error)) {
-      // TODO: Is there a way we can signal an IQ error
-      // back to the sender?
-      LOG(LS_WARNING) << "Invalid sources notify message: " << error.text;
-      return;
-    }
+  ViewRequest view_request;
+  ParseError error;
+  if (!ParseJingleViewRequest(action_elem, &view_request, &error)) {
+    LOG(LS_WARNING) << "Failed to parse view request: " << error.text;
+    return;
+  }
 
-    NamedSources::iterator it;
-    for (it = sources.mutable_audio()->begin();
-         it != sources.mutable_audio()->end(); ++it) {
-      bool found = false;
-      NamedSource found_source;
-      if (it->ssrc_set) {
-        found = media_sources_.GetAudioSourceBySsrc(it->ssrc, &found_source);
-      } else {
-        // For backwards compatibility, we remove by nick.
-        // TODO: Remove once all senders use explicit remove by ssrc.
-        found = media_sources_.GetFirstAudioSourceByNick(it->nick,
-                                                         &found_source);
-        if (found) {
-          it->SetSsrc(found_source.ssrc);
-          it->removed = true;
-        } else {
-          continue;  // No ssrc to remove.
-        }
-      }
-      if (it->removed && found) {
-        RemoveVoiceStream(session, found_source.ssrc);
-        media_sources_.RemoveAudioSourceBySsrc(it->ssrc);
-        updates.mutable_audio()->push_back(*it);
-        LOG(LS_INFO) << "Removed voice stream:  " << found_source.ssrc;
-      } else if (!it->removed && !found) {
-        AddVoiceStream(session, it->ssrc);
-        media_sources_.AddAudioSource(*it);
-        updates.mutable_audio()->push_back(*it);
-        LOG(LS_INFO) << "Added voice stream:  " << it->ssrc;
-      }
-    }
-    for (it = sources.mutable_video()->begin();
-         it != sources.mutable_video()->end(); ++it) {
-      bool found = false;
-      NamedSource found_source;
-      if (it->ssrc_set) {
-        found = media_sources_.GetVideoSourceBySsrc(it->ssrc, &found_source);
-      } else {
-        // For backwards compatibility, we remove by nick.
-        // TODO: Remove once all senders use explicit remove by ssrc.
-        found = media_sources_.GetFirstVideoSourceByNick(it->nick,
-                                                         &found_source);
-        if (found) {
-          it->SetSsrc(found_source.ssrc);
-          it->removed = true;
-        } else {
-          continue;  // No ssrc to remove.
-        }
-      }
-      if (it->removed && found) {
-        RemoveVideoStream(session, found_source.ssrc);
-        media_sources_.RemoveVideoSourceBySsrc(it->ssrc);
-        updates.mutable_video()->push_back(*it);
-        LOG(LS_INFO) << "Removed video stream:  " << found_source.ssrc;
-      } else if (!it->removed && !found) {
-        AddVideoStream(session, it->ssrc);
-        media_sources_.AddVideoSource(*it);
-        updates.mutable_video()->push_back(*it);
-        LOG(LS_INFO) << "Added video stream:  " << it->ssrc;
-      }
-    }
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (video_channel == NULL) {
+    LOG(LS_WARNING) << "Ignore view request since we have no video channel.";
+    return;
+  }
 
-    if (!updates.audio().empty() || !updates.video().empty()) {
-      SignalMediaSourcesUpdate(this, session, updates);
+  if (!video_channel->ApplyViewRequest(view_request)) {
+    LOG(LS_WARNING) << "Failed to ApplyViewRequest.";
+  }
+}
+
+void FindStreamChanges(const std::vector<StreamParams>& streams,
+                       const std::vector<StreamParams>& updates,
+                       std::vector<StreamParams>* added_streams,
+                       std::vector<StreamParams>* removed_streams) {
+  for (std::vector<StreamParams>::const_iterator update = updates.begin();
+       update != updates.end(); ++update) {
+    StreamParams stream;
+    if (GetStreamByNickAndName(streams, update->nick, update->name, &stream)) {
+      if (!update->has_ssrcs()) {
+        removed_streams->push_back(stream);
+      }
+    } else {
+      // There's a bug on reflector that will send <stream>s even
+      // though there is not ssrc (which means there isn't really a
+      // stream).  To work around it, we simply ignore new <stream>s
+      // that don't have any ssrcs.
+      if (update->has_ssrcs()) {
+        added_streams->push_back(*update);
+      }
     }
   }
 }
 
+void Call::OnRemoteDescriptionUpdate(BaseSession *base_session,
+                                     const ContentInfos& updated_contents) {
+  Session* session = static_cast<Session *>(base_session);
+
+  cricket::MediaStreams added_streams;
+  cricket::MediaStreams removed_streams;
+  std::vector<StreamParams>::const_iterator stream;
+
+  const ContentInfo* audio_content = GetFirstAudioContent(updated_contents);
+  if (audio_content) {
+    const AudioContentDescription* audio_update =
+        static_cast<const AudioContentDescription*>(audio_content->description);
+    if (!audio_update->codecs().empty()) {
+      UpdateVoiceChannelRemoteContent(session, audio_update);
+    }
+
+    FindStreamChanges(recv_streams_.audio(),
+                      audio_update->streams(),
+                      added_streams.mutable_audio(),
+                      removed_streams.mutable_audio());
+    for (stream = added_streams.audio().begin();
+         stream != added_streams.audio().end();
+         ++stream) {
+      AddAudioRecvStream(session, *stream);
+    }
+    for (stream = removed_streams.audio().begin();
+         stream != removed_streams.audio().end();
+         ++stream) {
+      RemoveAudioRecvStream(session, *stream);
+    }
+  }
+
+  const ContentInfo* video_content = GetFirstVideoContent(updated_contents);
+  if (video_content) {
+    const VideoContentDescription* video_update =
+        static_cast<const VideoContentDescription*>(video_content->description);
+    if (!video_update->codecs().empty()) {
+      UpdateVideoChannelRemoteContent(session, video_update);
+    }
+
+    FindStreamChanges(recv_streams_.video(),
+                      video_update->streams(),
+                      added_streams.mutable_video(),
+                      removed_streams.mutable_video());
+    for (stream = added_streams.video().begin();
+         stream != added_streams.video().end();
+         ++stream) {
+      AddVideoRecvStream(session, *stream);
+    }
+    for (stream = removed_streams.video().begin();
+         stream != removed_streams.video().end();
+         ++stream) {
+      RemoveVideoRecvStream(session, *stream);
+    }
+  }
+
+  if (!added_streams.empty() || !removed_streams.empty()) {
+    SignalMediaStreamsUpdate(this, session, added_streams, removed_streams);
+  }
+}
+
+bool Call::UpdateVoiceChannelRemoteContent(
+    Session* session, const AudioContentDescription* audio) {
+  VoiceChannel *voice_channel = GetVoiceChannel(session);
+  if (!voice_channel->SetRemoteContent(audio, CA_UPDATE)) {
+    LOG(LS_ERROR) << "Failure in audio SetRemoteContent with CA_UPDATE";
+    session->SetError(BaseSession::ERROR_CONTENT);
+    return false;
+  }
+  return true;
+}
+
+bool Call::UpdateVideoChannelRemoteContent(
+    Session* session, const VideoContentDescription* video) {
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (!video_channel->SetRemoteContent(video, CA_UPDATE)) {
+    LOG(LS_ERROR) << "Failure in video SetRemoteContent with CA_UPDATE";
+    session->SetError(BaseSession::ERROR_CONTENT);
+    return false;
+  }
+  return true;
+}
+
 void Call::OnReceivedTerminateReason(Session *session,
                                      const std::string &reason) {
   session_client_->session_manager()->signaling_thread()->Clear(this,
diff --git a/talk/session/phone/call.h b/talk/session/phone/call.h
index 357f884..e081c55 100644
--- a/talk/session/phone/call.h
+++ b/talk/session/phone/call.h
@@ -40,6 +40,7 @@
 #include "talk/session/phone/currentspeakermonitor.h"
 #include "talk/session/phone/mediamessages.h"
 #include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/streamparams.h"
 
 namespace cricket {
 
@@ -61,7 +62,7 @@
   void RejectSession(Session *session);
   void TerminateSession(Session *session);
   void Terminate();
-  bool SendViewRequest(Session* session,
+  bool SendViewRequest(Session *session,
                        const ViewRequest& view_request);
   void SetLocalRenderer(VideoRenderer* renderer);
   void SetVideoRenderer(Session *session, uint32 ssrc,
@@ -105,23 +106,30 @@
       SignalConnectionMonitor;
   sigslot::signal2<Call *, const VoiceMediaInfo&> SignalMediaMonitor;
   sigslot::signal2<Call *, const AudioInfo&> SignalAudioMonitor;
-  // Empty nick on NamedSource means "unknown".
-  // Ssrc of 0 on NamedSource means "no current speaker".
+  // Empty nick on StreamParams means "unknown".
+  // No ssrcs in StreamParams means "no current speaker".
   sigslot::signal3<Call *,
                    Session *,
-                   const NamedSource&> SignalSpeakerMonitor;
+                   const StreamParams&> SignalSpeakerMonitor;
   sigslot::signal2<Call *, const std::vector<ConnectionInfo> &>
       SignalVideoConnectionMonitor;
   sigslot::signal2<Call *, const VideoMediaInfo&> SignalVideoMediaMonitor;
-  sigslot::signal3<Call *,
+  // Gives added streams and removed streams, in that order.
+  sigslot::signal4<Call *,
                    Session *,
-                   const MediaSources&> SignalMediaSourcesUpdate;
+                   const MediaStreams&,
+                   const MediaStreams&> SignalMediaStreamsUpdate;
 
  private:
   void OnMessage(talk_base::Message *message);
   void OnSessionState(BaseSession *session, BaseSession::State state);
   void OnSessionError(BaseSession *session, Session::Error error);
-  void OnSessionInfo(Session *session, const buzz::XmlElement* action_elem);
+  void OnSessionInfoMessage(
+      Session *session, const buzz::XmlElement* action_elem);
+  void OnViewRequest(
+      Session *session, const ViewRequest& view_request);
+  void OnRemoteDescriptionUpdate(
+      BaseSession *session, const ContentInfos& updated_contents);
   void OnReceivedTerminateReason(Session *session, const std::string &reason);
   void IncomingSession(Session *session, const SessionDescription* offer);
   // Returns true on success.
@@ -137,18 +145,24 @@
   void OnConnectionMonitor(VideoChannel *channel,
                            const std::vector<ConnectionInfo> &infos);
   void OnMediaMonitor(VideoChannel *channel, const VideoMediaInfo& info);
-  VoiceChannel* GetVoiceChannel(Session* session);
-  VideoChannel* GetVideoChannel(Session* session);
-  void AddVoiceStream(Session *session, uint32 voice_ssrc);
-  void AddVideoStream(Session *session, uint32 video_ssrc);
-  void RemoveVoiceStream(Session *session, uint32 voice_ssrc);
-  void RemoveVideoStream(Session *session, uint32 video_ssrc);
+  VoiceChannel* GetVoiceChannel(Session *session);
+  VideoChannel* GetVideoChannel(Session *session);
+  bool UpdateVoiceChannelRemoteContent(Session *session,
+                                       const AudioContentDescription* audio);
+  bool UpdateVideoChannelRemoteContent(Session *session,
+                                       const VideoContentDescription* video);
+  void AddAudioRecvStream(Session *session, const StreamParams& audio_stream);
+  void AddVideoRecvStream(Session *session, const StreamParams& video_stream);
+  void RemoveAudioRecvStream(
+      Session *session, const StreamParams& audio_stream);
+  void RemoveVideoRecvStream(
+      Session *session, const StreamParams& video_stream);
   void ContinuePlayDTMF();
 
   uint32 id_;
   MediaSessionClient *session_client_;
   std::vector<Session *> sessions_;
-  MediaSources media_sources_;
+  MediaStreams recv_streams_;
   std::map<std::string, VoiceChannel *> voice_channel_map_;
   std::map<std::string, VideoChannel *> video_channel_map_;
   std::map<std::string, CurrentSpeakerMonitor *> speaker_monitor_map_;
diff --git a/talk/session/phone/channel.cc b/talk/session/phone/channel.cc
index 9cc88ff..1481887 100644
--- a/talk/session/phone/channel.cc
+++ b/talk/session/phone/channel.cc
@@ -33,16 +33,143 @@
 #include "talk/base/logging.h"
 #include "talk/p2p/base/transportchannel.h"
 #include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/mediamessages.h"
 #include "talk/session/phone/mediasessionclient.h"
 #include "talk/session/phone/rtcpmuxfilter.h"
 #include "talk/session/phone/rtputils.h"
 
 namespace cricket {
 
+enum {
+  MSG_ENABLE = 1,
+  MSG_DISABLE = 2,
+  MSG_MUTE = 3,
+  MSG_UNMUTE = 4,
+  MSG_SETREMOTECONTENT = 5,
+  MSG_SETLOCALCONTENT = 6,
+  MSG_EARLYMEDIATIMEOUT = 8,
+  MSG_PRESSDTMF = 9,
+  MSG_SETRENDERER = 10,
+  MSG_ADDRECVSTREAM = 11,
+  MSG_REMOVERECVSTREAM = 12,
+  MSG_SETRINGBACKTONE = 13,
+  MSG_PLAYRINGBACKTONE = 14,
+  MSG_SETMAXSENDBANDWIDTH = 15,
+  MSG_ADDSCREENCAST = 16,
+  MSG_REMOVESCREENCAST = 17,
+  // Removed MSG_SETRTCPCNAME = 18. It is no longer used.
+  MSG_SENDINTRAFRAME = 19,
+  MSG_REQUESTINTRAFRAME = 20,
+  MSG_SCREENCASTWINDOWEVENT = 21,
+  MSG_RTPPACKET = 22,
+  MSG_RTCPPACKET = 23,
+  MSG_CHANNEL_ERROR = 24,
+  MSG_ENABLECPUADAPTATION = 25,
+  MSG_DISABLECPUADAPTATION = 26,
+  MSG_SCALEVOLUME = 27,
+  MSG_HANDLEVIEWREQUEST = 28
+};
+
+struct SetContentData : public talk_base::MessageData {
+  SetContentData(const MediaContentDescription* content, ContentAction action)
+      : content(content),
+        action(action),
+        result(false) {
+  }
+  const MediaContentDescription* content;
+  ContentAction action;
+  bool result;
+};
+
+struct SetBandwidthData : public talk_base::MessageData {
+  explicit SetBandwidthData(int value) : value(value), result(false) {}
+  int value;
+  bool result;
+};
+
+struct SetRingbackToneMessageData : public talk_base::MessageData {
+  SetRingbackToneMessageData(const void* b, int l)
+      : buf(b),
+        len(l),
+        result(false) {
+  }
+  const void* buf;
+  int len;
+  bool result;
+};
+
+struct PlayRingbackToneMessageData : public talk_base::MessageData {
+  PlayRingbackToneMessageData(uint32 s, bool p, bool l)
+      : ssrc(s),
+        play(p),
+        loop(l),
+        result(false) {
+  }
+  uint32 ssrc;
+  bool play;
+  bool loop;
+  bool result;
+};
+struct DtmfMessageData : public talk_base::MessageData {
+  DtmfMessageData(int d, bool p)
+      : digit(d),
+        playout(p),
+        result(false) {
+  }
+  int digit;
+  bool playout;
+  bool result;
+};
+struct ScaleVolumeMessageData : public talk_base::MessageData {
+  ScaleVolumeMessageData(uint32 s, double l, double r)
+      : ssrc(s),
+        left(l),
+        right(r),
+        result(false) {
+  }
+  uint32 ssrc;
+  double left;
+  double right;
+  bool result;
+};
+
 struct PacketMessageData : public talk_base::MessageData {
   talk_base::Buffer packet;
 };
 
+struct RenderMessageData : public talk_base::MessageData {
+  RenderMessageData(uint32 s, VideoRenderer* r) : ssrc(s), renderer(r) {}
+  uint32 ssrc;
+  VideoRenderer* renderer;
+};
+
+struct ScreencastMessageData : public talk_base::MessageData {
+  ScreencastMessageData(uint32 s, const ScreencastId& id)
+      : ssrc(s),
+        window_id(id) {
+  }
+  uint32 ssrc;
+  ScreencastId window_id;
+};
+
+struct ScreencastEventData : public talk_base::MessageData {
+  ScreencastEventData(uint32 s, talk_base::WindowEvent we)
+      : ssrc(s),
+        event(we) {
+  }
+  uint32 ssrc;
+  talk_base::WindowEvent event;
+};
+
+struct ViewRequestMessageData : public talk_base::MessageData {
+  explicit ViewRequestMessageData(const ViewRequest& r)
+      : request(r),
+        result(false) {
+  }
+  ViewRequest request;
+  bool result;
+};
+
 struct VoiceChannelErrorMessageData : public talk_base::MessageData {
   VoiceChannelErrorMessageData(uint32 in_ssrc,
                                VoiceMediaChannel::Error in_error)
@@ -61,6 +188,18 @@
   VideoMediaChannel::Error error;
 };
 
+struct SsrcMessageData : public talk_base::MessageData {
+  explicit SsrcMessageData(uint32 ssrc) : ssrc(ssrc), result(false) {}
+  uint32 ssrc;
+  bool result;
+};
+
+struct StreamMessageData : public talk_base::MessageData {
+  explicit StreamMessageData(const StreamParams& sp) : sp(sp), result(false) {}
+  StreamParams sp;
+  bool result;
+};
+
 static const char* PacketType(bool rtcp) {
   return (!rtcp) ? "RTP" : "RTCP";
 }
@@ -125,35 +264,34 @@
       this, &BaseChannel::OnChannelRead);
 
   session_->SignalState.connect(this, &BaseChannel::OnSessionState);
-  session_->SignalRemoteDescriptionUpdate.connect(this,
-      &BaseChannel::OnRemoteDescriptionUpdate);
 
   OnSessionState(session(), session()->state());
   set_rtcp_transport_channel(rtcp_transport_channel);
   return true;
 }
 
+// Can be called from thread other than worker thread
 bool BaseChannel::Enable(bool enable) {
-  // Can be called from thread other than worker thread
   Send(enable ? MSG_ENABLE : MSG_DISABLE);
   return true;
 }
 
+// Can be called from thread other than worker thread
 bool BaseChannel::Mute(bool mute) {
-  // Can be called from thread other than worker thread
+  Clear(MSG_UNMUTE);  // Clear any penging auto-unmutes.
   Send(mute ? MSG_MUTE : MSG_UNMUTE);
   return true;
 }
 
-bool BaseChannel::RemoveStream(uint32 ssrc) {
-  StreamMessageData data(ssrc, 0);
-  Send(MSG_REMOVESTREAM, &data);
-  return true;
+bool BaseChannel::AddRecvStream(const StreamParams& sp) {
+  StreamMessageData data(sp);
+  Send(MSG_ADDRECVSTREAM, &data);
+  return data.result;
 }
 
-bool BaseChannel::SetRtcpCName(const std::string& cname) {
-  SetRtcpCNameData data(cname);
-  Send(MSG_SETRTCPCNAME, &data);
+bool BaseChannel::RemoveRecvStream(uint32 ssrc) {
+  SsrcMessageData data(ssrc);
+  Send(MSG_REMOVERECVSTREAM, &data);
   return data.result;
 }
 
@@ -346,6 +484,14 @@
     return;
   }
 
+  // If this channel is suppose to handle RTP data, that is determined by
+  // checking against ssrc filter. This is necessary to do it here to avoid
+  // double decryption.
+  if (ssrc_filter_.IsActive() &&
+      !ssrc_filter_.DemuxPacket(packet->data(), packet->length(), rtcp)) {
+    return;
+  }
+
   // Signal to the media sink before unprotecting the packet. TODO:
   // Separate APIs to record unprotected media and protected header.
   {
@@ -429,16 +575,6 @@
   }
 }
 
-void BaseChannel::OnRemoteDescriptionUpdate(BaseSession* session) {
-  const MediaContentDescription* content =
-      GetFirstContent(session->remote_description());
-
-  if (content && !SetRemoteContent(content, CA_UPDATE)) {
-    LOG(LS_ERROR) << "Failure in SetRemoteContent with CA_UPDATE";
-    session->SetError(BaseSession::ERROR_CONTENT);
-  }
-}
-
 void BaseChannel::EnableMedia_w() {
   ASSERT(worker_thread_ == talk_base::Thread::Current());
   if (enabled_)
@@ -509,10 +645,6 @@
   return media_channel()->SetSendBandwidth(true, max_bandwidth);
 }
 
-bool BaseChannel::SetRtcpCName_w(const std::string& cname) {
-  return media_channel()->SetRtcpCName(cname);
-}
-
 bool BaseChannel::SetSrtp_w(const std::vector<CryptoParams>& cryptos,
                             ContentAction action, ContentSource src) {
   bool ret;
@@ -549,6 +681,177 @@
   return ret;
 }
 
+bool BaseChannel::AddRecvStream_w(const StreamParams& sp) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  if (!media_channel()->AddRecvStream(sp))
+    return false;
+
+  return ssrc_filter_.AddStream(sp);
+}
+
+bool BaseChannel::RemoveRecvStream_w(uint32 ssrc) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  ssrc_filter_.RemoveStream(ssrc);
+  return media_channel()->RemoveRecvStream(ssrc);
+}
+
+bool BaseChannel::UpdateLocalStreams_w(const std::vector<StreamParams>& streams,
+                                       ContentAction action) {
+  if (!VERIFY(action == CA_OFFER || action == CA_ANSWER || action == CA_UPDATE))
+    return false;
+
+  // If this is an update, streams only contain streams that have changed.
+  if (action == CA_UPDATE) {
+    for (StreamParamsVec::const_iterator it = streams.begin();
+         it != streams.end(); ++it) {
+      StreamParams existing_stream;
+      bool stream_exist = GetStreamByNickAndName(local_streams_, it->nick,
+                                                 it->name, &existing_stream);
+      if (!stream_exist && it->has_ssrcs()) {
+        if (media_channel()->AddSendStream(*it)) {
+          local_streams_.push_back(*it);
+          LOG(LS_INFO) << "Add send stream ssrc: " << it->first_ssrc();
+        } else {
+          LOG(LS_INFO) << "Failed to add send stream ssrc: "
+                       << it->first_ssrc();
+          return false;
+        }
+      } else if (stream_exist && !it->has_ssrcs()) {
+        if (!media_channel()->RemoveSendStream(existing_stream.first_ssrc())) {
+            LOG(LS_ERROR) << "Failed to remove send stream with ssrc "
+                          << it->first_ssrc() << ".";
+            return false;
+        }
+        RemoveStreamBySsrc(&local_streams_, existing_stream.first_ssrc());
+      } else {
+        LOG(LS_WARNING) << "Ignore unsupported stream update";
+      }
+    }
+    return true;
+  }
+  // Else streams are all the streams we want to send.
+
+  // Check for streams that have been removed.
+  bool ret = true;
+  for (StreamParamsVec::const_iterator it = local_streams_.begin();
+       it != local_streams_.end(); ++it) {
+    if (!GetStreamBySsrc(streams, it->first_ssrc(), NULL)) {
+      if (!media_channel()->RemoveSendStream(it->first_ssrc())) {
+        LOG(LS_ERROR) << "Failed to remove send stream with ssrc "
+                      << it->first_ssrc() << ".";
+        ret = false;
+      }
+    }
+  }
+  // Check for new streams.
+  for (StreamParamsVec::const_iterator it = streams.begin();
+       it != streams.end(); ++it) {
+    if (!GetStreamBySsrc(local_streams_, it->first_ssrc(), NULL)) {
+      if (media_channel()->AddSendStream(*it)) {
+        LOG(LS_INFO) << "Add send ssrc: " << it->ssrcs[0];
+      } else {
+        LOG(LS_INFO) << "Failed to add send stream ssrc: " << it->first_ssrc();
+        ret = false;
+      }
+    }
+  }
+  local_streams_ = streams;
+  return ret;
+}
+
+bool BaseChannel::UpdateRemoteStreams_w(
+    const std::vector<StreamParams>& streams,
+    ContentAction action) {
+  // If this is an update, streams only contain streams that have changed.
+  if (action == CA_UPDATE) {
+    for (StreamParamsVec::const_iterator it = streams.begin();
+         it != streams.end(); ++it) {
+      StreamParams existing_stream;
+      bool stream_exists = GetStreamByNickAndName(remote_streams_, it->nick,
+                                                  it->name, &existing_stream);
+      if (!stream_exists && it->has_ssrcs()) {
+        if (AddRecvStream_w(*it)) {
+          remote_streams_.push_back(*it);
+          LOG(LS_INFO) << "Add remote stream ssrc: " << it->first_ssrc();
+        } else {
+          LOG(LS_INFO) << "Failed to add remote stream ssrc: "
+                       << it->first_ssrc();
+          return false;
+        }
+      } else if (stream_exists && !it->has_ssrcs()) {
+        if (!RemoveRecvStream_w(existing_stream.first_ssrc())) {
+            LOG(LS_ERROR) << "Failed to remove remote stream with ssrc "
+                          << it->first_ssrc() << ".";
+            return false;
+        }
+        RemoveStreamBySsrc(&remote_streams_, existing_stream.first_ssrc());
+      } else {
+        LOG(LS_WARNING) << "Ignore unsupported stream update";
+      }
+    }
+    return true;
+  }
+  // Else streams are all the streams we want to receive.
+
+  // Check for streams that have been removed.
+  bool ret = true;
+  for (StreamParamsVec::const_iterator it = remote_streams_.begin();
+       it != remote_streams_.end(); ++it) {
+    if (!GetStreamBySsrc(streams, it->first_ssrc(), NULL)) {
+      if (!RemoveRecvStream_w(it->first_ssrc())) {
+        LOG(LS_ERROR) << "Failed to remove remote stream with ssrc "
+                      << it->first_ssrc() << ".";
+        ret = false;
+      }
+    }
+  }
+  // Check for new streams.
+  for (StreamParamsVec::const_iterator it = streams.begin();
+      it != streams.end(); ++it) {
+    if (!GetStreamBySsrc(remote_streams_, it->first_ssrc(), NULL)) {
+      if (AddRecvStream_w(*it)) {
+        LOG(LS_INFO) << "Add remote ssrc: " << it->ssrcs[0];
+      } else {
+        LOG(LS_INFO) << "Failed to add remote stream ssrc: "
+                     << it->first_ssrc();
+        ret = false;
+      }
+    }
+  }
+  remote_streams_ = streams;
+  return ret;
+}
+
+bool BaseChannel::SetBaseLocalContent_w(const MediaContentDescription* content,
+                                        ContentAction action) {
+  bool ret = UpdateLocalStreams_w(content->streams(), action);
+  // Set local SRTP parameters (what we will encrypt with).
+  ret &= SetSrtp_w(content->cryptos(), action, CS_LOCAL);
+  // Set local RTCP mux parameters.
+  ret &= SetRtcpMux_w(content->rtcp_mux(), action, CS_LOCAL);
+  // Set local RTP header extensions.
+  if (content->rtp_header_extensions_set()) {
+    ret &= media_channel()->SetRecvRtpHeaderExtensions(
+        content->rtp_header_extensions());
+  }
+  return ret;
+}
+
+bool BaseChannel::SetBaseRemoteContent_w(const MediaContentDescription* content,
+                                         ContentAction action) {
+  bool ret = UpdateRemoteStreams_w(content->streams(), action);
+  // Set remote SRTP parameters (what the other side will encrypt with).
+  ret &= SetSrtp_w(content->cryptos(), action, CS_REMOTE);
+  // Set remote RTCP mux parameters.
+  ret &= SetRtcpMux_w(content->rtcp_mux(), action, CS_REMOTE);
+  // Set remote RTP header extensions.
+  if (content->rtp_header_extensions_set()) {
+    ret &= media_channel()->SetSendRtpHeaderExtensions(
+        content->rtp_header_extensions());
+  }
+  return ret;
+}
+
 void BaseChannel::OnMessage(talk_base::Message *pmsg) {
   switch (pmsg->message_id) {
     case MSG_ENABLE:
@@ -564,13 +867,6 @@
     case MSG_UNMUTE:
       UnmuteMedia_w();
       break;
-
-    case MSG_SETRTCPCNAME: {
-      SetRtcpCNameData* data = static_cast<SetRtcpCNameData*>(pmsg->pdata);
-      data->result = SetRtcpCName_w(data->cname);
-      break;
-    }
-
     case MSG_SETLOCALCONTENT: {
       SetContentData* data = static_cast<SetContentData*>(pmsg->pdata);
       data->result = SetLocalContent_w(data->content, data->action);
@@ -581,13 +877,16 @@
       data->result = SetRemoteContent_w(data->content, data->action);
       break;
     }
-
-    case MSG_REMOVESTREAM: {
+    case MSG_ADDRECVSTREAM: {
       StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
-      RemoveStream_w(data->ssrc1);
+      data->result = AddRecvStream_w(data->sp);
       break;
     }
-
+    case MSG_REMOVERECVSTREAM: {
+      SsrcMessageData* data = static_cast<SsrcMessageData*>(pmsg->pdata);
+      data->result = RemoveRecvStream_w(data->ssrc);
+      break;
+    }
     case MSG_SETMAXSENDBANDWIDTH: {
       SetBandwidthData* data = static_cast<SetBandwidthData*>(pmsg->pdata);
       data->result = SetMaxSendBandwidth_w(data->value);
@@ -641,7 +940,9 @@
                            bool rtcp)
     : BaseChannel(thread, media_engine, media_channel, session, content_name,
                   rtcp),
-      received_media_(false) {
+      received_media_(false),
+      mute_on_type_(false),
+      mute_on_type_timeout_(kTypingBlackoutPeriod) {
 }
 
 VoiceChannel::~VoiceChannel() {
@@ -665,12 +966,6 @@
   return true;
 }
 
-bool VoiceChannel::AddStream(uint32 ssrc) {
-  StreamMessageData data(ssrc, 0);
-  Send(MSG_ADDSTREAM, &data);
-  return true;
-}
-
 bool VoiceChannel::SetRingbackTone(const void* buf, int len) {
   SetRingbackToneMessageData data(buf, len);
   Send(MSG_SETRINGBACKTONE, &data);
@@ -774,7 +1069,7 @@
   if (!media_channel()->SetPlayout(recv)) {
     SendLastMediaError();
   }
-    
+
   // Send outgoing data if we're the active call, we have the remote content,
   // and we have had some form of connectivity.
   bool send = enabled() && has_remote_content() && was_ever_writable();
@@ -804,27 +1099,17 @@
   const AudioContentDescription* audio =
       static_cast<const AudioContentDescription*>(content);
   ASSERT(audio != NULL);
+  if (!audio) return false;
 
-  bool ret;
-  if (audio->ssrc_set()) {
-    media_channel()->SetSendSsrc(audio->ssrc());
-    LOG(LS_INFO) << "Set send ssrc for audio: " << audio->ssrc();
+  bool ret = SetBaseLocalContent_w(content, action);
+  // Set local audio codecs (what we want to receive).
+  // TODO: Change action != CA_UPDATE to !audio->partial() when partial
+  // is set properly.
+  if (action != CA_UPDATE || audio->has_codecs()) {
+    ret &= media_channel()->SetRecvCodecs(audio->codecs());
   }
-  // set SRTP
-  ret = SetSrtp_w(audio->cryptos(), action, CS_LOCAL);
-  // set RTCP mux
-  if (ret) {
-    ret = SetRtcpMux_w(audio->rtcp_mux(), action, CS_LOCAL);
-  }
-  // set payload type and config for voice codecs
-  if (ret) {
-    ret = media_channel()->SetRecvCodecs(audio->codecs());
-  }
-  // set header extensions
-  if (ret && audio->rtp_header_extensions_set()) {
-    ret = media_channel()->SetRecvRtpHeaderExtensions(
-        audio->rtp_header_extensions());
-  }
+
+  // If everything worked, see if we can start receiving.
   if (ret) {
     set_has_local_content(true);
     ChangeState();
@@ -842,34 +1127,32 @@
   const AudioContentDescription* audio =
       static_cast<const AudioContentDescription*>(content);
   ASSERT(audio != NULL);
+  if (!audio) return false;
 
-  bool ret;
-  // set SRTP
-  ret = SetSrtp_w(audio->cryptos(), action, CS_REMOTE);
-  // set RTCP mux
-  if (ret) {
-    ret = SetRtcpMux_w(audio->rtcp_mux(), action, CS_REMOTE);
-  }
-  // set codecs and payload types
-  if (ret) {
-    ret = media_channel()->SetSendCodecs(audio->codecs());
-  }
-  // set header extensions
-  if (ret && audio->rtp_header_extensions_set()) {
-    ret = media_channel()->SetSendRtpHeaderExtensions(
-        audio->rtp_header_extensions());
+  bool ret = true;
+  // Set remote video codecs (what the other side wants to receive).
+  if (action != CA_UPDATE || audio->has_codecs()) {
+    ret &= media_channel()->SetSendCodecs(audio->codecs());
   }
 
-  int audio_options = 0;
-  if (audio->conference_mode()) {
-    audio_options |= OPT_CONFERENCE;
-  }
-  if (!media_channel()->SetOptions(audio_options)) {
-    // Log an error on failure, but don't abort the call.
-    LOG(LS_ERROR) << "Failed to set voice channel options";
+  ret &= SetBaseRemoteContent_w(content, action);
+
+  if (action != CA_UPDATE) {
+    // Tweak our audio processing settings, if needed.
+    int audio_options = 0;
+    if (audio->conference_mode()) {
+      audio_options |= OPT_CONFERENCE;
+    }
+    if (audio->agc_minus_10db()) {
+      audio_options |= OPT_AGC_MINUS_10DB;
+    }
+    if (!media_channel()->SetOptions(audio_options)) {
+      // Log an error on failure, but don't abort the call.
+      LOG(LS_ERROR) << "Failed to set voice channel options";
+    }
   }
 
-  // update state
+  // If everything worked, see if we can start sending.
   if (ret) {
     set_has_remote_content(true);
     ChangeState();
@@ -879,15 +1162,6 @@
   return ret;
 }
 
-void VoiceChannel::AddStream_w(uint32 ssrc) {
-  ASSERT(worker_thread() == talk_base::Thread::Current());
-  media_channel()->AddStream(ssrc);
-}
-
-void VoiceChannel::RemoveStream_w(uint32 ssrc) {
-  media_channel()->RemoveStream(ssrc);
-}
-
 bool VoiceChannel::SetRingbackTone_w(const void* buf, int len) {
   ASSERT(worker_thread() == talk_base::Thread::Current());
   return media_channel()->SetRingbackTone(static_cast<const char*>(buf), len);
@@ -925,11 +1199,6 @@
 
 void VoiceChannel::OnMessage(talk_base::Message *pmsg) {
   switch (pmsg->message_id) {
-    case MSG_ADDSTREAM: {
-      StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
-      AddStream_w(data->ssrc1);
-      break;
-    }
     case MSG_SETRINGBACKTONE: {
       SetRingbackToneMessageData* data =
           static_cast<SetRingbackToneMessageData*>(pmsg->pdata);
@@ -963,7 +1232,6 @@
       delete data;
       break;
     }
-
     default:
       BaseChannel::OnMessage(pmsg);
       break;
@@ -987,9 +1255,14 @@
 }
 
 void VoiceChannel::OnVoiceChannelError(
-    uint32 ssrc, VoiceMediaChannel::Error error) {
-  VoiceChannelErrorMessageData *data = new VoiceChannelErrorMessageData(
-      ssrc, error);
+    uint32 ssrc, VoiceMediaChannel::Error err) {
+  if (err == VoiceMediaChannel::ERROR_REC_TYPING_NOISE_DETECTED &&
+      mute_on_type_ && !muted()) {
+    Mute(true);
+    PostDelayed(mute_on_type_timeout_, MSG_UNMUTE, NULL);
+  }
+  VoiceChannelErrorMessageData* data = new VoiceChannelErrorMessageData(
+      ssrc, err);
   signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
 }
 
@@ -1036,6 +1309,8 @@
           rtcp_channel)) {
     return false;
   }
+  media_channel()->SignalScreencastWindowEvent.connect(
+      this, &VideoChannel::OnScreencastWindowEvent);
   media_channel()->SignalMediaError.connect(
       this, &VideoChannel::OnVideoChannelError);
   srtp_filter()->SignalSrtpError.connect(
@@ -1056,19 +1331,29 @@
   DisableMedia_w();
 }
 
-bool VideoChannel::AddStream(uint32 ssrc, uint32 voice_ssrc) {
-  StreamMessageData data(ssrc, voice_ssrc);
-  Send(MSG_ADDSTREAM, &data);
-  return true;
-}
-
 bool VideoChannel::SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
   RenderMessageData data(ssrc, renderer);
   Send(MSG_SETRENDERER, &data);
   return true;
 }
 
+bool VideoChannel::ApplyViewRequest(const ViewRequest& request) {
+  ViewRequestMessageData data(request);
+  Send(MSG_HANDLEVIEWREQUEST, &data);
+  return data.result;
+}
 
+bool VideoChannel::AddScreencast(uint32 ssrc, const ScreencastId& id) {
+  ScreencastMessageData data(ssrc, id);
+  Send(MSG_ADDSCREENCAST, &data);
+  return true;
+}
+
+bool VideoChannel::RemoveScreencast(uint32 ssrc) {
+  ScreencastMessageData data(ssrc, ScreencastId());
+  Send(MSG_REMOVESCREENCAST, &data);
+  return true;
+}
 
 bool VideoChannel::SendIntraFrame() {
   Send(MSG_SENDINTRAFRAME);
@@ -1136,26 +1421,15 @@
   const VideoContentDescription* video =
       static_cast<const VideoContentDescription*>(content);
   ASSERT(video != NULL);
+  if (!video) return false;
 
-  bool ret;
-  if (video->ssrc_set()) {
-    media_channel()->SetSendSsrc(video->ssrc());
-    LOG(LS_INFO) << "Set send ssrc for video: " << video->ssrc();
+  bool ret = SetBaseLocalContent_w(content, action);
+  // Set local video codecs (what we want to receive).
+  if (action != CA_UPDATE || video->has_codecs()) {
+    ret &= media_channel()->SetRecvCodecs(video->codecs());
   }
-  // set SRTP
-  ret = SetSrtp_w(video->cryptos(), action, CS_LOCAL);
-  // set RTCP mux
-  if (ret) {
-    ret = SetRtcpMux_w(video->rtcp_mux(), action, CS_LOCAL);
-  }
-  // set payload types and config for receiving video
-  if (ret) {
-    ret = media_channel()->SetRecvCodecs(video->codecs());
-  }
-  if (ret && video->rtp_header_extensions_set()) {
-    ret = media_channel()->SetRecvRtpHeaderExtensions(
-        video->rtp_header_extensions());
-  }
+
+  // If everything worked, see if we can start receiving.
   if (ret) {
     set_has_local_content(true);
     ChangeState();
@@ -1173,28 +1447,33 @@
   const VideoContentDescription* video =
       static_cast<const VideoContentDescription*>(content);
   ASSERT(video != NULL);
+  if (!video) return false;
 
-  bool ret;
-  // set SRTP
-  ret = SetSrtp_w(video->cryptos(), action, CS_REMOTE);
-  // set RTCP mux
-  if (ret) {
-    ret = SetRtcpMux_w(video->rtcp_mux(), action, CS_REMOTE);
+  bool ret = true;
+  // Set remote video codecs (what the other side wants to receive).
+  if (action != CA_UPDATE || video->has_codecs()) {
+    ret &= media_channel()->SetSendCodecs(video->codecs());
   }
-  // Set video bandwidth parameters.
-  if (ret) {
+
+  ret &= SetBaseRemoteContent_w(content, action);
+
+  if (action != CA_UPDATE) {
+    // Tweak our video processing settings, if needed.
+    int video_options = 0;
+    if (video->conference_mode()) {
+      video_options |= OPT_CONFERENCE;
+    }
+    if (!media_channel()->SetOptions(video_options)) {
+      // Log an error on failure, but don't abort the call.
+      LOG(LS_ERROR) << "Failed to set video channel options";
+    }
+    // Set bandwidth parameters (what the other side wants to get, default=auto)
     int bandwidth_bps = video->bandwidth();
     bool auto_bandwidth = (bandwidth_bps == kAutoBandwidth);
-    ret = media_channel()->SetSendBandwidth(auto_bandwidth, bandwidth_bps);
+    ret &= media_channel()->SetSendBandwidth(auto_bandwidth, bandwidth_bps);
   }
-  if (ret) {
-    ret = media_channel()->SetSendCodecs(video->codecs());
-  }
-  // set header extensions
-  if (ret && video->rtp_header_extensions_set()) {
-    ret = media_channel()->SetSendRtpHeaderExtensions(
-        video->rtp_header_extensions());
-  }
+
+  // If everything worked, see if we can start sending.
   if (ret) {
     set_has_remote_content(true);
     ChangeState();
@@ -1204,31 +1483,87 @@
   return ret;
 }
 
-void VideoChannel::AddStream_w(uint32 ssrc, uint32 voice_ssrc) {
-  media_channel()->AddStream(ssrc, voice_ssrc);
-}
+bool VideoChannel::ApplyViewRequest_w(const ViewRequest& request) {
+  bool ret = true;
+  // Set the send format for each of the local streams. If the view request
+  // does not contain a local stream, set its send format to 0x0, which will
+  // drop all frames.
+  for (std::vector<StreamParams>::const_iterator it = local_streams().begin();
+      it != local_streams().end(); ++it) {
+    VideoFormat format(0, 0, 0, cricket::FOURCC_I420);
+    StaticVideoViews::const_iterator view;
+    for (view = request.static_video_views.begin();
+        view != request.static_video_views.end(); ++view) {
+      // Sender view request from Reflector has SSRC 0 (b/5977302). Here we hack
+      // the client to apply the view request with SSRC 0. TODO: Remove
+      // 0 == view->SSRC once Reflector uses the correct SSRC in view request.
+      if (it->has_ssrc(view->ssrc) || 0 == view->ssrc) {
+        format.width = view->width;
+        format.height = view->height;
+        format.interval = cricket::VideoFormat::FpsToInterval(view->framerate);
+        break;
+      }
+    }
 
-void VideoChannel::RemoveStream_w(uint32 ssrc) {
-  media_channel()->RemoveStream(ssrc);
+    ret &= media_channel()->SetSendStreamFormat(it->first_ssrc(), format);
+  }
+
+  // Check if the view request has invalid streams.
+  for (StaticVideoViews::const_iterator it = request.static_video_views.begin();
+      it != request.static_video_views.end(); ++it) {
+    if (!GetStreamBySsrc(local_streams(), it->ssrc, NULL)) {
+      LOG(LS_WARNING) << "View request's SSRC " << it->ssrc
+                      << " is not in the local streams.";
+    }
+  }
+
+  return ret;
 }
 
 void VideoChannel::SetRenderer_w(uint32 ssrc, VideoRenderer* renderer) {
   media_channel()->SetRenderer(ssrc, renderer);
 }
 
+void VideoChannel::AddScreencast_w(uint32 ssrc, const ScreencastId& id) {
+  media_channel()->AddScreencast(ssrc, id);
+}
+
+void VideoChannel::RemoveScreencast_w(uint32 ssrc) {
+  media_channel()->RemoveScreencast(ssrc);
+}
+
+void VideoChannel::OnScreencastWindowEvent_s(uint32 ssrc,
+                                             talk_base::WindowEvent we) {
+  ASSERT(signaling_thread() == talk_base::Thread::Current());
+  SignalScreencastWindowEvent(ssrc, we);
+}
 
 void VideoChannel::OnMessage(talk_base::Message *pmsg) {
   switch (pmsg->message_id) {
-    case MSG_ADDSTREAM: {
-      StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
-      AddStream_w(data->ssrc1, data->ssrc2);
-      break;
-    }
     case MSG_SETRENDERER: {
       RenderMessageData* data = static_cast<RenderMessageData*>(pmsg->pdata);
       SetRenderer_w(data->ssrc, data->renderer);
       break;
     }
+    case MSG_ADDSCREENCAST: {
+      ScreencastMessageData* data =
+          static_cast<ScreencastMessageData*>(pmsg->pdata);
+      AddScreencast_w(data->ssrc, data->window_id);
+      break;
+    }
+    case MSG_REMOVESCREENCAST: {
+      ScreencastMessageData* data =
+          static_cast<ScreencastMessageData*>(pmsg->pdata);
+      RemoveScreencast_w(data->ssrc);
+      break;
+    }
+    case MSG_SCREENCASTWINDOWEVENT: {
+      ScreencastEventData* data =
+          static_cast<ScreencastEventData*>(pmsg->pdata);
+      OnScreencastWindowEvent_s(data->ssrc, data->event);
+      delete data;
+      break;
+    }
     case MSG_SENDINTRAFRAME:
       SendIntraFrame_w();
       break;
@@ -1248,6 +1583,12 @@
       delete data;
       break;
     }
+    case MSG_HANDLEVIEWREQUEST: {
+      ViewRequestMessageData* data =
+          static_cast<ViewRequestMessageData*>(pmsg->pdata);
+      data->result = ApplyViewRequest_w(data->request);
+      break;
+    }
     default:
       BaseChannel::OnMessage(pmsg);
       break;
@@ -1265,6 +1606,11 @@
   SignalMediaMonitor(this, info);
 }
 
+void VideoChannel::OnScreencastWindowEvent(uint32 ssrc,
+                                           talk_base::WindowEvent event) {
+  ScreencastEventData* pdata = new ScreencastEventData(ssrc, event);
+  signaling_thread()->Post(this, MSG_SCREENCASTWINDOWEVENT, pdata);
+}
 
 void VideoChannel::OnVideoChannelError(uint32 ssrc,
                                        VideoMediaChannel::Error error) {
diff --git a/talk/session/phone/channel.h b/talk/session/phone/channel.h
index 324c35c..df0124f 100644
--- a/talk/session/phone/channel.h
+++ b/talk/session/phone/channel.h
@@ -35,6 +35,7 @@
 #include "talk/base/criticalsection.h"
 #include "talk/base/network.h"
 #include "talk/base/sigslot.h"
+#include "talk/base/window.h"
 #include "talk/p2p/client/socketmonitor.h"
 #include "talk/p2p/base/session.h"
 #include "talk/session/phone/audiomonitor.h"
@@ -42,38 +43,16 @@
 #include "talk/session/phone/mediaengine.h"
 #include "talk/session/phone/mediamonitor.h"
 #include "talk/session/phone/rtcpmuxfilter.h"
+#include "talk/session/phone/screencastid.h"
+#include "talk/session/phone/ssrcmuxfilter.h"
+#include "talk/session/phone/streamparams.h"
 #include "talk/session/phone/srtpfilter.h"
 
 namespace cricket {
 
 class MediaContentDescription;
 struct CryptoParams;
-
-enum {
-  MSG_ENABLE = 1,
-  MSG_DISABLE = 2,
-  MSG_MUTE = 3,
-  MSG_UNMUTE = 4,
-  MSG_SETREMOTECONTENT = 5,
-  MSG_SETLOCALCONTENT = 6,
-  MSG_EARLYMEDIATIMEOUT = 8,
-  MSG_PRESSDTMF = 9,
-  MSG_SETRENDERER = 10,
-  MSG_ADDSTREAM = 11,
-  MSG_REMOVESTREAM = 12,
-  MSG_SETRINGBACKTONE = 13,
-  MSG_PLAYRINGBACKTONE = 14,
-  MSG_SETMAXSENDBANDWIDTH = 15,
-  MSG_SETRTCPCNAME = 18,
-  MSG_SENDINTRAFRAME = 19,
-  MSG_REQUESTINTRAFRAME = 20,
-  MSG_RTPPACKET = 22,
-  MSG_RTCPPACKET = 23,
-  MSG_CHANNEL_ERROR = 24,
-  MSG_ENABLECPUADAPTATION = 25,
-  MSG_DISABLECPUADAPTATION = 26,
-  MSG_SCALEVOLUME = 27
-};
+struct ViewRequest;
 
 // BaseChannel contains logic common to voice and video, including
 // enable/mute, marshaling calls to a worker thread, and
@@ -102,7 +81,6 @@
   bool secure() const { return srtp_filter_.IsActive(); }
 
   // Channel control
-  bool SetRtcpCName(const std::string& cname);
   bool SetLocalContent(const MediaContentDescription* content,
                        ContentAction action);
   bool SetRemoteContent(const MediaContentDescription* content,
@@ -113,7 +91,8 @@
   bool Mute(bool mute);
 
   // Multiplexing
-  bool RemoveStream(uint32 ssrc);
+  bool AddRecvStream(const StreamParams& sp);
+  bool RemoveRecvStream(uint32 ssrc);
 
   // Monitoring
   void StartConnectionMonitor(int cms);
@@ -159,6 +138,15 @@
     return !SignalRecvPacket.is_empty();
   }
 
+  SsrcMuxFilter* ssrc_filter() { return &ssrc_filter_; }
+
+  const std::vector<StreamParams>& local_streams() const {
+    return local_streams_;
+  }
+  const std::vector<StreamParams>& remote_streams() const {
+    return remote_streams_;
+  }
+
  protected:
   MediaEngineInterface* media_engine() const { return media_engine_; }
   virtual MediaChannel* media_channel() const { return media_channel_; }
@@ -199,7 +187,6 @@
 
   // Setting the send codec based on the remote description.
   void OnSessionState(BaseSession* session, BaseSession::State state);
-  void OnRemoteDescriptionUpdate(BaseSession* session);
 
   void EnableMedia_w();
   void DisableMedia_w();
@@ -207,38 +194,24 @@
   void UnmuteMedia_w();
   void ChannelWritable_w();
   void ChannelNotWritable_w();
-
-  struct StreamMessageData : public talk_base::MessageData {
-    StreamMessageData(uint32 s1, uint32 s2) : ssrc1(s1), ssrc2(s2) {}
-    uint32 ssrc1;
-    uint32 ssrc2;
-  };
-  virtual void RemoveStream_w(uint32 ssrc) = 0;
+  bool AddRecvStream_w(const StreamParams& sp);
+  bool RemoveRecvStream_w(uint32 ssrc);
 
   virtual void ChangeState() = 0;
 
-  struct SetRtcpCNameData : public talk_base::MessageData {
-    explicit SetRtcpCNameData(const std::string& cname)
-        : cname(cname), result(false) {}
-    std::string cname;
-    bool result;
-  };
-  bool SetRtcpCName_w(const std::string& cname);
-
-  struct SetContentData : public talk_base::MessageData {
-    SetContentData(const MediaContentDescription* content,
-                   ContentAction action)
-        : content(content), action(action), result(false) {}
-    const MediaContentDescription* content;
-    ContentAction action;
-    bool result;
-  };
-
   // Gets the content appropriate to the channel (audio or video).
   virtual const MediaContentDescription* GetFirstContent(
       const SessionDescription* sdesc) = 0;
+  bool UpdateLocalStreams_w(const std::vector<StreamParams>& streams,
+                            ContentAction action);
+  bool UpdateRemoteStreams_w(const std::vector<StreamParams>& streams,
+                             ContentAction action);
+  bool SetBaseLocalContent_w(const MediaContentDescription* content,
+                             ContentAction action);
   virtual bool SetLocalContent_w(const MediaContentDescription* content,
                                  ContentAction action) = 0;
+  bool SetBaseRemoteContent_w(const MediaContentDescription* content,
+                              ContentAction action);
   virtual bool SetRemoteContent_w(const MediaContentDescription* content,
                                   ContentAction action) = 0;
 
@@ -246,11 +219,6 @@
                  ContentSource src);
   bool SetRtcpMux_w(bool enable, ContentAction action, ContentSource src);
 
-  struct SetBandwidthData : public talk_base::MessageData {
-    explicit SetBandwidthData(int value) : value(value), result(false) {}
-    int value;
-    bool result;
-  };
   bool SetMaxSendBandwidth_w(int max_bandwidth);
 
   // From MessageHandler
@@ -270,6 +238,8 @@
   MediaEngineInterface *media_engine_;
   BaseSession *session_;
   MediaChannel *media_channel_;
+  std::vector<StreamParams> local_streams_;
+  std::vector<StreamParams> remote_streams_;
 
   std::string content_name_;
   bool rtcp_;
@@ -277,6 +247,7 @@
   TransportChannel *rtcp_transport_channel_;
   SrtpFilter srtp_filter_;
   RtcpMuxFilter rtcp_mux_filter_;
+  SsrcMuxFilter ssrc_filter_;
   talk_base::scoped_ptr<SocketMonitor> socket_monitor_;
   bool enabled_;
   bool writable_;
@@ -301,9 +272,6 @@
     return static_cast<VoiceMediaChannel*>(BaseChannel::media_channel());
   }
 
-  // Add an incoming stream with the specified SSRC.
-  bool AddStream(uint32 ssrc);
-
   bool SetRingbackTone(const void* buf, int len);
   void SetEarlyMedia(bool enable);
   // This signal is emitted when we have gone a period of time without
@@ -314,6 +282,10 @@
   bool PlayRingbackTone(uint32 ssrc, bool play, bool loop);
   bool PressDTMF(int digit, bool playout);
   bool SetOutputScaling(uint32 ssrc, double left, double right);
+  void set_mute_on_type(bool enable, int timeout) {
+    mute_on_type_ = enable;
+    mute_on_type_timeout_ = talk_base::_max(0, timeout);
+  }
 
   // Monitoring functions
   sigslot::signal2<VoiceChannel*, const std::vector<ConnectionInfo> &>
@@ -337,52 +309,9 @@
   sigslot::signal3<VoiceChannel*, uint32, VoiceMediaChannel::Error>
       SignalMediaError;
 
- private:
-  struct SetRingbackToneMessageData : public talk_base::MessageData {
-    SetRingbackToneMessageData(const void* b, int l)
-        : buf(b),
-          len(l),
-          result(false) {
-    }
-    const void* buf;
-    int len;
-    bool result;
-  };
-  struct PlayRingbackToneMessageData : public talk_base::MessageData {
-    PlayRingbackToneMessageData(uint32 s, bool p, bool l)
-        : ssrc(s),
-          play(p),
-          loop(l),
-          result(false) {
-    }
-    uint32 ssrc;
-    bool play;
-    bool loop;
-    bool result;
-  };
-  struct DtmfMessageData : public talk_base::MessageData {
-    DtmfMessageData(int d, bool p)
-        : digit(d),
-          playout(p),
-          result(false) {
-    }
-    int digit;
-    bool playout;
-    bool result;
-  };
-  struct ScaleVolumeMessageData : public talk_base::MessageData {
-    ScaleVolumeMessageData(uint32 s, double l, double r)
-        : ssrc(s),
-          left(l),
-          right(r),
-          result(false) {
-    }
-    uint32 ssrc;
-    double left;
-    double right;
-    bool result;
-  };
+  static const int kTypingBlackoutPeriod = 1500;
 
+ private:
   // overrides from BaseChannel
   virtual void OnChannelRead(TransportChannel* channel,
                              const char *data, size_t len);
@@ -394,9 +323,6 @@
   virtual bool SetRemoteContent_w(const MediaContentDescription* content,
                                   ContentAction action);
 
-  void AddStream_w(uint32 ssrc);
-  void RemoveStream_w(uint32 ssrc);
-
   bool SetRingbackTone_w(const void* buf, int len);
   bool PlayRingbackTone_w(uint32 ssrc, bool play, bool loop);
   void HandleEarlyMediaTimeout();
@@ -417,6 +343,8 @@
   bool received_media_;
   talk_base::scoped_ptr<VoiceMediaMonitor> media_monitor_;
   talk_base::scoped_ptr<AudioMonitor> audio_monitor_;
+  bool mute_on_type_;
+  int  mute_on_type_timeout_;
 };
 
 // VideoChannel is a specialization for video.
@@ -434,11 +362,11 @@
     return static_cast<VideoMediaChannel*>(BaseChannel::media_channel());
   }
 
-  // Add an incoming stream with the specified SSRC.
-  bool AddStream(uint32 ssrc, uint32 voice_ssrc);
-
   bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
+  bool ApplyViewRequest(const ViewRequest& request);
 
+  bool AddScreencast(uint32 ssrc, const ScreencastId& id);
+  bool RemoveScreencast(uint32 ssrc);
 
   sigslot::signal2<VideoChannel*, const std::vector<ConnectionInfo> &>
       SignalConnectionMonitor;
@@ -446,6 +374,7 @@
   void StartMediaMonitor(int cms);
   void StopMediaMonitor();
   sigslot::signal2<VideoChannel*, const VideoMediaInfo&> SignalMediaMonitor;
+  sigslot::signal2<uint32, talk_base::WindowEvent> SignalScreencastWindowEvent;
 
   bool SendIntraFrame();
   bool RequestIntraFrame();
@@ -464,9 +393,6 @@
   virtual bool SetRemoteContent_w(const MediaContentDescription* content,
                                   ContentAction action);
 
-  void AddStream_w(uint32 ssrc, uint32 voice_ssrc);
-  void RemoveStream_w(uint32 ssrc);
-
   void SendIntraFrame_w() {
     media_channel()->SendIntraFrame();
   }
@@ -480,24 +406,24 @@
     media_channel()->SetOptions(enable ? OPT_CPU_ADAPTATION : 0);
   }
 
-  struct RenderMessageData : public talk_base::MessageData {
-    RenderMessageData(uint32 s, VideoRenderer* r) : ssrc(s), renderer(r) {}
-    uint32 ssrc;
-    VideoRenderer* renderer;
-  };
-
-
+  bool ApplyViewRequest_w(const ViewRequest& request);
   void SetRenderer_w(uint32 ssrc, VideoRenderer* renderer);
 
+  void AddScreencast_w(uint32 ssrc, const ScreencastId&);
+  void RemoveScreencast_w(uint32 ssrc);
+  void OnScreencastWindowEvent_s(uint32 ssrc, talk_base::WindowEvent we);
 
   virtual void OnMessage(talk_base::Message *pmsg);
   virtual void OnConnectionMonitorUpdate(
       SocketMonitor *monitor, const std::vector<ConnectionInfo> &infos);
   virtual void OnMediaMonitorUpdate(
       VideoMediaChannel *media_channel, const VideoMediaInfo& info);
+  virtual void OnScreencastWindowEvent(uint32 ssrc,
+                                       talk_base::WindowEvent event);
   void OnVideoChannelError(uint32 ssrc, VideoMediaChannel::Error error);
   void OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode, SrtpFilter::Error error);
 
+
   VoiceChannel *voice_channel_;
   VideoRenderer *renderer_;
   talk_base::scoped_ptr<VideoMediaMonitor> media_monitor_;
diff --git a/talk/session/phone/channel_unittest.cc b/talk/session/phone/channel_unittest.cc
new file mode 100644
index 0000000..fa6c938
--- /dev/null
+++ b/talk/session/phone/channel_unittest.cc
@@ -0,0 +1,1916 @@
+// libjingle
+// Copyright 2009 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/signalthread.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/fakertp.h"
+#include "talk/session/phone/mediamessages.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/mediarecorder.h"
+#include "talk/session/phone/rtpdump.h"
+
+using cricket::CA_OFFER;
+using cricket::CA_ANSWER;
+using cricket::CA_UPDATE;
+using cricket::StreamParams;
+
+static const cricket::AudioCodec kPcmuCodec(0, "PCMU", 64000, 8000, 1, 0);
+static const cricket::AudioCodec kPcmaCodec(8, "PCMA", 64000, 8000, 1, 0);
+static const cricket::AudioCodec kIsacCodec(103, "ISAC", 40000, 16000, 1, 0);
+static const cricket::VideoCodec kH264Codec(97, "H264", 640, 400, 30, 0);
+static const cricket::VideoCodec kH264SvcCodec(99, "H264-SVC", 320, 200, 15, 0);
+static const uint32 kSsrc1 = 0x1111;
+static const uint32 kSsrc2 = 0x2222;
+static const uint32 kSsrc3 = 0x3333;
+static const char kCName[] = "a@b.com";
+
+class VoiceTraits {
+ public:
+  typedef cricket::VoiceChannel Channel;
+  typedef cricket::FakeVoiceMediaChannel MediaChannel;
+  typedef cricket::AudioContentDescription Content;
+  typedef cricket::AudioCodec Codec;
+  typedef cricket::VoiceMediaInfo MediaInfo;
+};
+
+class VideoTraits {
+ public:
+  typedef cricket::VideoChannel Channel;
+  typedef cricket::FakeVideoMediaChannel MediaChannel;
+  typedef cricket::VideoContentDescription Content;
+  typedef cricket::VideoCodec Codec;
+  typedef cricket::VideoMediaInfo MediaInfo;
+};
+
+// Base class for Voice/VideoChannel tests
+template<class T>
+class ChannelTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+  enum Flags { RTCP = 0x1, RTCP_MUX = 0x2, SECURE = 0x4, SSRC_MUX = 0x8 };
+  ChannelTest(const uint8* rtp_data, int rtp_len,
+              const uint8* rtcp_data, int rtcp_len)
+      : media_channel1_(NULL),
+        media_channel2_(NULL),
+        rtp_packet_(reinterpret_cast<const char*>(rtp_data), rtp_len),
+        rtcp_packet_(reinterpret_cast<const char*>(rtcp_data), rtcp_len),
+        media_info_callbacks1_(),
+        media_info_callbacks2_(),
+        ssrc_(0),
+        error_(T::MediaChannel::ERROR_NONE) {
+  }
+
+  void CreateChannels(int flags1, int flags2) {
+    CreateChannels(new typename T::MediaChannel(NULL),
+                   new typename T::MediaChannel(NULL),
+                   flags1, flags2, talk_base::Thread::Current());
+  }
+  void CreateChannels(int flags) {
+     CreateChannels(new typename T::MediaChannel(NULL),
+                    new typename T::MediaChannel(NULL),
+                    flags, talk_base::Thread::Current());
+  }
+  void CreateChannels(int flags1, int flags2,
+                      talk_base::Thread* thread) {
+    CreateChannels(new typename T::MediaChannel(NULL),
+                   new typename T::MediaChannel(NULL),
+                   flags1, flags2, thread);
+  }
+  void CreateChannels(int flags,
+                      talk_base::Thread* thread) {
+    CreateChannels(new typename T::MediaChannel(NULL),
+                     new typename T::MediaChannel(NULL),
+                     flags, thread);
+  }
+  void CreateChannels(
+      typename T::MediaChannel* ch1, typename T::MediaChannel* ch2,
+      int flags1, int flags2, talk_base::Thread* thread) {
+    media_channel1_ = ch1;
+    media_channel2_ = ch2;
+    channel1_.reset(CreateChannel(thread, &media_engine_, ch1, &session1_,
+                                  (flags1 & RTCP) != 0));
+    channel2_.reset(CreateChannel(thread, &media_engine_, ch2, &session2_,
+                                  (flags2 & RTCP) != 0));
+    channel1_->SignalMediaMonitor.connect(
+        this, &ChannelTest<T>::OnMediaMonitor);
+    channel2_->SignalMediaMonitor.connect(
+        this, &ChannelTest<T>::OnMediaMonitor);
+    channel1_->SignalMediaError.connect(
+        this, &ChannelTest<T>::OnMediaChannelError);
+    channel2_->SignalMediaError.connect(
+        this, &ChannelTest<T>::OnMediaChannelError);
+    CreateContent(flags1, kPcmuCodec, kH264Codec, &local_media_content1_);
+    CreateContent(flags2, kPcmuCodec, kH264Codec, &local_media_content2_);
+    CopyContent(local_media_content1_, &remote_media_content1_);
+    CopyContent(local_media_content2_, &remote_media_content2_);
+    // Add stream information (SSRC) to the local content but not to the remote
+    // content. This means that we per default know the SSRC of what we send but
+    // not what we receive.
+    AddLegacyStreamInContent(kSsrc1, flags1, &local_media_content1_);
+    AddLegacyStreamInContent(kSsrc2, flags2, &local_media_content2_);
+
+    // If SSRC_MUX is used we also need to know the SSRC of the incoming stream.
+    if (flags1 & SSRC_MUX) {
+      AddLegacyStreamInContent(kSsrc1, flags1, &remote_media_content1_);
+    }
+    if (flags2 & SSRC_MUX) {
+      AddLegacyStreamInContent(kSsrc2, flags2, &remote_media_content2_);
+    }
+  }
+
+  void CreateChannels(
+      typename T::MediaChannel* ch1, typename T::MediaChannel* ch2,
+      int flags, talk_base::Thread* thread) {
+    media_channel1_ = ch1;
+    media_channel2_ = ch2;
+    channel1_.reset(CreateChannel(thread, &media_engine_, ch1, &session1_,
+                                  (flags & RTCP) != 0));
+    channel2_.reset(CreateChannel(thread, &media_engine_, ch2, &session1_,
+                                  (flags & RTCP) != 0));
+    channel1_->SignalMediaMonitor.connect(
+        this, &ChannelTest<T>::OnMediaMonitor);
+    channel2_->SignalMediaMonitor.connect(
+        this, &ChannelTest<T>::OnMediaMonitor);
+    channel2_->SignalMediaError.connect(
+        this, &ChannelTest<T>::OnMediaChannelError);
+    CreateContent(flags, kPcmuCodec, kH264Codec, &local_media_content1_);
+    CreateContent(flags, kPcmuCodec, kH264Codec, &local_media_content2_);
+    CopyContent(local_media_content1_, &remote_media_content1_);
+    CopyContent(local_media_content2_, &remote_media_content2_);
+    // Add stream information (SSRC) to the local content but not to the remote
+    // content. This means that we per default know the SSRC of what we send but
+    // not what we receive.
+    AddLegacyStreamInContent(kSsrc1, flags, &local_media_content1_);
+    AddLegacyStreamInContent(kSsrc2, flags, &local_media_content2_);
+
+    // If SSRC_MUX is used we also need to know the SSRC of the incoming stream.
+    if (flags & SSRC_MUX) {
+      AddLegacyStreamInContent(kSsrc1, flags, &remote_media_content1_);
+      AddLegacyStreamInContent(kSsrc2, flags, &remote_media_content2_);
+    }
+  }
+  typename T::Channel* CreateChannel(talk_base::Thread* thread,
+                                     cricket::MediaEngineInterface* engine,
+                                     typename T::MediaChannel* ch,
+                                     cricket::BaseSession* session,
+                                     bool rtcp) {
+    typename T::Channel* channel = new typename T::Channel(
+        thread, engine, ch, session, cricket::CN_AUDIO, rtcp);
+    if (!channel->Init()) {
+      delete channel;
+      channel = NULL;
+    }
+    return channel;
+  }
+
+  bool SendInitiate() {
+    bool result = channel1_->SetLocalContent(&local_media_content1_, CA_OFFER);
+    if (result) {
+      channel1_->Enable(true);
+      result = channel2_->SetRemoteContent(&remote_media_content1_, CA_OFFER);
+      if (result) {
+        result = channel2_->SetLocalContent(&local_media_content2_, CA_ANSWER);
+        if (result) {
+          session1_.Connect(&session2_);
+        }
+      }
+    }
+    return result;
+  }
+  bool SendAccept() {
+    channel2_->Enable(true);
+    return channel1_->SetRemoteContent(&remote_media_content2_, CA_ANSWER);
+  }
+  bool SendTerminate() {
+    channel1_.reset();
+    channel2_.reset();
+    return true;
+  }
+
+  bool AddStream1(int id) {
+    return channel1_->AddRecvStream(cricket::StreamParams::CreateLegacy(id));
+  }
+  bool RemoveStream1(int id) {
+    return channel1_->RemoveRecvStream(id);
+  }
+
+  cricket::FakeTransport* GetTransport1() {
+    return session1_.GetTransport(channel1_->content_name());
+  }
+  cricket::FakeTransport* GetTransport2() {
+    return session2_.GetTransport(channel2_->content_name());
+  }
+
+  bool SendRtp1() {
+    return media_channel1_->SendRtp(rtp_packet_.c_str(), rtp_packet_.size());
+  }
+  bool SendRtp2() {
+    return media_channel2_->SendRtp(rtp_packet_.c_str(), rtp_packet_.size());
+  }
+  bool SendRtcp1() {
+    return media_channel1_->SendRtcp(rtcp_packet_.c_str(), rtcp_packet_.size());
+  }
+  bool SendRtcp2() {
+    return media_channel2_->SendRtcp(rtcp_packet_.c_str(), rtcp_packet_.size());
+  }
+  // Methods to send custom data.
+  bool SendCustomRtp1(uint32 ssrc) {
+    std::string data(CreateRtpData(ssrc));
+    return media_channel1_->SendRtp(data.c_str(), data.size());
+  }
+  bool SendCustomRtp2(uint32 ssrc) {
+    std::string data(CreateRtpData(ssrc));
+    return media_channel2_->SendRtp(data.c_str(), data.size());
+  }
+  bool SendCustomRtcp1(uint32 ssrc) {
+    std::string data(CreateRtcpData(ssrc));
+    return media_channel1_->SendRtcp(data.c_str(), data.size());
+  }
+  bool SendCustomRtcp2(uint32 ssrc) {
+    std::string data(CreateRtcpData(ssrc));
+    return media_channel2_->SendRtcp(data.c_str(), data.size());
+  }
+  bool CheckRtp1() {
+    return media_channel1_->CheckRtp(rtp_packet_.c_str(), rtp_packet_.size());
+  }
+  bool CheckRtp2() {
+    return media_channel2_->CheckRtp(rtp_packet_.c_str(), rtp_packet_.size());
+  }
+  bool CheckRtcp1() {
+    return media_channel1_->CheckRtcp(rtcp_packet_.c_str(),
+                                      rtcp_packet_.size());
+  }
+  bool CheckRtcp2() {
+    return media_channel2_->CheckRtcp(rtcp_packet_.c_str(),
+                                      rtcp_packet_.size());
+  }
+  // Methods to check custom data.
+  bool CheckCustomRtp1(uint32 ssrc) {
+    std::string data(CreateRtpData(ssrc));
+    return media_channel1_->CheckRtp(data.c_str(), data.size());
+  }
+  bool CheckCustomRtp2(uint32 ssrc) {
+    std::string data(CreateRtpData(ssrc));
+    return media_channel2_->CheckRtp(data.c_str(), data.size());
+  }
+  bool CheckCustomRtcp1(uint32 ssrc) {
+    std::string data(CreateRtcpData(ssrc));
+    return media_channel1_->CheckRtcp(data.c_str(), data.size());
+  }
+  bool CheckCustomRtcp2(uint32 ssrc) {
+    std::string data(CreateRtcpData(ssrc));
+    return media_channel2_->CheckRtcp(data.c_str(), data.size());
+  }
+  std::string CreateRtpData(uint32 ssrc) {
+    std::string data(rtp_packet_);
+    // Set SSRC in the rtp packet copy.
+    talk_base::SetBE32(const_cast<char*>(data.c_str()) + 8, ssrc);
+    return data;
+  }
+  std::string CreateRtcpData(uint32 ssrc) {
+    std::string data(rtcp_packet_);
+    // Set SSRC in the rtcp packet copy.
+    talk_base::SetBE32(const_cast<char*>(data.c_str()) + 4, ssrc);
+    return data;
+  }
+
+  bool CheckNoRtp1() {
+    return media_channel1_->CheckNoRtp();
+  }
+  bool CheckNoRtp2() {
+    return media_channel2_->CheckNoRtp();
+  }
+  bool CheckNoRtcp1() {
+    return media_channel1_->CheckNoRtcp();
+  }
+  bool CheckNoRtcp2() {
+    return media_channel2_->CheckNoRtcp();
+  }
+
+  void CreateContent(int flags,
+                     const cricket::AudioCodec& audio_codec,
+                     const cricket::VideoCodec& video_codec,
+                     typename T::Content* content) {
+    // overridden in specialized classes
+  }
+  void CopyContent(const typename T::Content& source,
+                   typename T::Content* content) {
+    // overridden in specialized classes
+  }
+
+  class CallThread : public talk_base::SignalThread {
+   public:
+    typedef bool (ChannelTest<T>::*Method)();
+    CallThread(ChannelTest<T>* obj, Method method, bool* result)
+        : obj_(obj),
+          method_(method),
+          result_(result) {
+      *result = false;
+    }
+    virtual void DoWork() {
+      bool result = (*obj_.*method_)();
+      if (result_) {
+        *result_ = result;
+      }
+    }
+   private:
+    ChannelTest<T>* obj_;
+    Method method_;
+    bool* result_;
+  };
+  void CallOnThread(typename CallThread::Method method, bool* result) {
+    CallThread* thread = new CallThread(this, method, result);
+    thread->Start();
+    thread->Release();
+  }
+
+  void CallOnThreadAndWaitForDone(typename CallThread::Method method,
+                                  bool* result) {
+    CallThread* thread = new CallThread(this, method, result);
+    thread->Start();
+    thread->Destroy(true);
+  }
+
+  bool CodecMatches(const typename T::Codec& c1, const typename T::Codec& c2) {
+    return false;  // overridden in specialized classes
+  }
+
+  void OnMediaMonitor(typename T::Channel* channel,
+                      const typename T::MediaInfo& info) {
+    if (channel == channel1_.get()) {
+      media_info_callbacks1_++;
+    } else if (channel == channel2_.get()) {
+      media_info_callbacks2_++;
+    }
+  }
+
+  void OnMediaChannelError(typename T::Channel* channel,
+                           uint32 ssrc,
+                           typename T::MediaChannel::Error error) {
+    ssrc_ = ssrc;
+    error_ = error;
+  }
+
+  void AddLegacyStreamInContent(uint32 ssrc, int flags,
+                        typename T::Content* content) {
+    // Base implementation.
+  }
+
+  // Tests that can be used by derived classes.
+
+  // Basic sanity check.
+  void TestInit() {
+    CreateChannels(0, 0);
+    EXPECT_FALSE(channel1_->secure());
+    EXPECT_FALSE(media_channel1_->sending());
+    EXPECT_FALSE(media_channel1_->playout());
+    EXPECT_TRUE(media_channel1_->codecs().empty());
+    EXPECT_TRUE(media_channel1_->recv_streams().empty());
+    EXPECT_TRUE(media_channel1_->rtp_packets().empty());
+    EXPECT_TRUE(media_channel1_->rtcp_packets().empty());
+  }
+
+  // Test that SetLocalContent and SetRemoteContent properly configure
+  // the codecs.
+  void TestSetContents() {
+    CreateChannels(0, 0);
+    typename T::Content content;
+    CreateContent(0, kPcmuCodec, kH264Codec, &content);
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, CA_OFFER));
+    EXPECT_EQ(0U, media_channel1_->codecs().size());
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_ANSWER));
+    ASSERT_EQ(1U, media_channel1_->codecs().size());
+    EXPECT_TRUE(CodecMatches(content.codecs()[0],
+                             media_channel1_->codecs()[0]));
+  }
+
+  // Test that SetLocalContent and SetRemoteContent properly deals
+  // with an empty offer.
+  void TestSetContentsNullOffer() {
+    CreateChannels(0, 0);
+    typename T::Content content;
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, CA_OFFER));
+    CreateContent(0, kPcmuCodec, kH264Codec, &content);
+    EXPECT_EQ(0U, media_channel1_->codecs().size());
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_ANSWER));
+    ASSERT_EQ(1U, media_channel1_->codecs().size());
+    EXPECT_TRUE(CodecMatches(content.codecs()[0],
+                             media_channel1_->codecs()[0]));
+  }
+
+  // Test that SetLocalContent and SetRemoteContent properly set RTCP
+  // mux.
+  void TestSetContentsRtcpMux() {
+    CreateChannels(RTCP, RTCP);
+    EXPECT_TRUE(channel1_->rtcp_transport_channel() != NULL);
+    EXPECT_TRUE(channel2_->rtcp_transport_channel() != NULL);
+    typename T::Content content;
+    CreateContent(0, kPcmuCodec, kH264Codec, &content);
+    // Both sides agree on mux. Should no longer be a separate RTCP channel.
+    content.set_rtcp_mux(true);
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, CA_OFFER));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_ANSWER));
+    EXPECT_TRUE(channel1_->rtcp_transport_channel() == NULL);
+    // Only initiator supports mux. Should still have a separate RTCP channel.
+    EXPECT_TRUE(channel2_->SetLocalContent(&content, CA_OFFER));
+    content.set_rtcp_mux(false);
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content, CA_ANSWER));
+    EXPECT_TRUE(channel2_->rtcp_transport_channel() != NULL);
+  }
+
+  // Test that SetRemoteContent properly deals with a content update.
+  void TestSetRemoteContentUpdate() {
+    CreateChannels(0, 0);
+    typename T::Content content;
+    CreateContent(RTCP | RTCP_MUX | SECURE, kPcmuCodec, kH264Codec, &content);
+    EXPECT_EQ(0U, media_channel1_->codecs().size());
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, CA_OFFER));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_ANSWER));
+    ASSERT_EQ(1U, media_channel1_->codecs().size());
+    EXPECT_TRUE(CodecMatches(content.codecs()[0],
+                             media_channel1_->codecs()[0]));
+    // Now update with other codecs.
+    typename T::Content update_content;
+    update_content.set_partial(true);
+    CreateContent(0, kIsacCodec, kH264SvcCodec, &update_content);
+    EXPECT_TRUE(channel1_->SetRemoteContent(&update_content, CA_UPDATE));
+    ASSERT_EQ(1U, media_channel1_->codecs().size());
+    EXPECT_TRUE(CodecMatches(update_content.codecs()[0],
+                             media_channel1_->codecs()[0]));
+    // Now update without any codecs. This is ignored.
+    typename T::Content empty_content;
+    empty_content.set_partial(true);
+    EXPECT_TRUE(channel1_->SetRemoteContent(&empty_content, CA_UPDATE));
+    ASSERT_EQ(1U, media_channel1_->codecs().size());
+    EXPECT_TRUE(CodecMatches(update_content.codecs()[0],
+                             media_channel1_->codecs()[0]));
+  }
+
+  // Test that Add/RemoveStream properly forward to the media channel.
+  void TestStreams() {
+    CreateChannels(0, 0);
+    EXPECT_TRUE(AddStream1(1));
+    EXPECT_TRUE(AddStream1(2));
+    EXPECT_EQ(2U, media_channel1_->recv_streams().size());
+    EXPECT_TRUE(RemoveStream1(2));
+    EXPECT_EQ(1U, media_channel1_->recv_streams().size());
+    EXPECT_TRUE(RemoveStream1(1));
+    EXPECT_EQ(0U, media_channel1_->recv_streams().size());
+  }
+
+  // Test that SetLocalContent properly handles adding and removing StreamParams
+  // to the local content description.
+  // This test uses the CA_UPDATE action that don't require a full
+  // MediaContentDescription to do an update.
+  void TestUpdateStreamsInLocalContent() {
+    cricket::StreamParams stream1;
+    stream1.name = "Stream1";
+    stream1.nick = "1";
+    stream1.ssrcs.push_back(kSsrc1);
+    stream1.cname = "stream1_cname";
+
+    cricket::StreamParams stream2;
+    stream2.name = "Stream2";
+    stream2.nick = "2";
+    stream2.ssrcs.push_back(kSsrc2);
+    stream2.cname = "stream2_cname";
+
+    cricket::StreamParams stream3;
+    stream3.name = "Stream3";
+    stream3.nick = "3";
+    stream3.ssrcs.push_back(kSsrc3);
+    stream3.cname = "stream3_cname";
+
+    CreateChannels(0, 0);
+    typename T::Content content1;
+    CreateContent(0, kPcmuCodec, kH264Codec, &content1);
+    content1.AddStream(stream1);
+    EXPECT_EQ(0u, media_channel1_->send_streams().size());
+    EXPECT_TRUE(channel1_->SetLocalContent(&content1, CA_OFFER));
+
+    ASSERT_EQ(1u, media_channel1_->send_streams().size());
+    EXPECT_EQ(stream1, media_channel1_->send_streams()[0]);
+
+    // Update the local streams by adding another sending stream.
+    // Use a partial updated session description.
+    typename T::Content content2;
+    content2.AddStream(stream2);
+    content2.AddStream(stream3);
+    content2.set_partial(true);
+    EXPECT_TRUE(channel1_->SetLocalContent(&content2, CA_UPDATE));
+    ASSERT_EQ(3u, media_channel1_->send_streams().size());
+    EXPECT_EQ(stream1, media_channel1_->send_streams()[0]);
+    EXPECT_EQ(stream2, media_channel1_->send_streams()[1]);
+    EXPECT_EQ(stream3, media_channel1_->send_streams()[2]);
+
+    // Update the local streams by removing the first sending stream.
+    // This is done by removing all SSRCS for this particular stream.
+    typename T::Content content3;
+    stream1.ssrcs.clear();
+    content3.AddStream(stream1);
+    content3.set_partial(true);
+    EXPECT_TRUE(channel1_->SetLocalContent(&content3, CA_UPDATE));
+    ASSERT_EQ(2u, media_channel1_->send_streams().size());
+    EXPECT_EQ(stream2, media_channel1_->send_streams()[0]);
+    EXPECT_EQ(stream3, media_channel1_->send_streams()[1]);
+
+    // Update the local streams with a stream that does not change.
+    // THe update is ignored.
+    typename T::Content content4;
+    content4.AddStream(stream2);
+    content4.set_partial(true);
+    EXPECT_TRUE(channel1_->SetLocalContent(&content4, CA_UPDATE));
+    ASSERT_EQ(2u, media_channel1_->send_streams().size());
+    EXPECT_EQ(stream2, media_channel1_->send_streams()[0]);
+    EXPECT_EQ(stream3, media_channel1_->send_streams()[1]);
+  }
+
+  // Test that SetRemoteContent properly handles adding and removing
+  // StreamParams to the remote content description.
+  // This test uses the CA_UPDATE action that don't require a full
+  // MediaContentDescription to do an update.
+  void TestUpdateStreamsInRemoteContent() {
+    cricket::StreamParams stream1;
+    stream1.name = "Stream1";
+    stream1.nick = "1";
+    stream1.ssrcs.push_back(kSsrc1);
+    stream1.cname = "stream1_cname";
+
+    cricket::StreamParams stream2;
+    stream2.name = "Stream2";
+    stream2.nick = "2";
+    stream2.ssrcs.push_back(kSsrc2);
+    stream2.cname = "stream2_cname";
+
+    cricket::StreamParams stream3;
+    stream3.name = "Stream3";
+    stream3.nick = "3";
+    stream3.ssrcs.push_back(kSsrc3);
+    stream3.cname = "stream3_cname";
+
+    CreateChannels(0, 0);
+    typename T::Content content1;
+    CreateContent(0, kPcmuCodec, kH264Codec, &content1);
+    content1.AddStream(stream1);
+    EXPECT_EQ(0u, media_channel1_->recv_streams().size());
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content1, CA_OFFER));
+
+    ASSERT_EQ(1u, media_channel1_->codecs().size());
+    ASSERT_EQ(1u, media_channel1_->recv_streams().size());
+    EXPECT_EQ(stream1, media_channel1_->recv_streams()[0]);
+
+    // Update the remote streams by adding another sending stream.
+    // Use a partial updated session description.
+    typename T::Content content2;
+    content2.AddStream(stream2);
+    content2.AddStream(stream3);
+    content2.set_partial(true);
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, CA_UPDATE));
+    ASSERT_EQ(3u, media_channel1_->recv_streams().size());
+    EXPECT_EQ(stream1, media_channel1_->recv_streams()[0]);
+    EXPECT_EQ(stream2, media_channel1_->recv_streams()[1]);
+    EXPECT_EQ(stream3, media_channel1_->recv_streams()[2]);
+
+    // Update the remote streams by removing the first stream.
+    // This is done by removing all SSRCS for this particular stream.
+    typename T::Content content3;
+    stream1.ssrcs.clear();
+    content3.AddStream(stream1);
+    content3.set_partial(true);
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content3, CA_UPDATE));
+    ASSERT_EQ(2u, media_channel1_->recv_streams().size());
+    EXPECT_EQ(stream2, media_channel1_->recv_streams()[0]);
+    EXPECT_EQ(stream3, media_channel1_->recv_streams()[1]);
+
+    // Update the remote streams with a stream that does not change.
+    // The update is ignored.
+    typename T::Content content4;
+    content4.AddStream(stream2);
+    content4.set_partial(true);
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content4, CA_UPDATE));
+    ASSERT_EQ(2u, media_channel1_->recv_streams().size());
+    EXPECT_EQ(stream2, media_channel1_->recv_streams()[0]);
+    EXPECT_EQ(stream3, media_channel1_->recv_streams()[1]);
+  }
+
+  // Test that SetLocalContent and SetRemoteContent properly
+  // handles adding and removing StreamParams when the action is a full
+  // CA_OFFER / CA_ANSWER.
+  void TestChangeStreamParamsInContent() {
+    cricket::StreamParams stream1;
+    stream1.name = "Stream1";
+    stream1.ssrcs.push_back(kSsrc1);
+    stream1.cname = "stream1_cname";
+
+    cricket::StreamParams stream2;
+    stream2.name = "Stream2";
+    stream2.ssrcs.push_back(kSsrc2);
+    stream2.cname = "stream2_cname";
+
+    // Setup a call where channel 1 send |stream1| to channel 2.
+    CreateChannels(0, 0);
+    typename T::Content content1;
+    CreateContent(0, kPcmuCodec, kH264Codec, &content1);
+    content1.AddStream(stream1);
+    EXPECT_TRUE(channel1_->SetLocalContent(&content1, CA_OFFER));
+    EXPECT_TRUE(channel1_->Enable(true));
+    EXPECT_EQ(1u, media_channel1_->send_streams().size());
+
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content1, CA_OFFER));
+    EXPECT_EQ(1u, media_channel2_->recv_streams().size());
+    session1_.Connect(&session2_);
+
+    // Channel 2 do not send anything.
+    typename T::Content content2;
+    CreateContent(0, kPcmuCodec, kH264Codec, &content2);
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, CA_ANSWER));
+    EXPECT_EQ(0u, media_channel1_->recv_streams().size());
+    EXPECT_TRUE(channel2_->SetLocalContent(&content2, CA_ANSWER));
+    EXPECT_TRUE(channel2_->Enable(true));
+    EXPECT_EQ(0u, media_channel2_->send_streams().size());
+
+    EXPECT_TRUE(SendCustomRtp1(kSsrc1));
+    EXPECT_TRUE(CheckCustomRtp2(kSsrc1));
+
+    // Let channel 2 update the content by sending |stream2| and enable SRTP.
+    typename T::Content content3;
+    CreateContent(SECURE, kPcmuCodec, kH264Codec, &content3);
+    content3.AddStream(stream2);
+    EXPECT_TRUE(channel2_->SetLocalContent(&content3, CA_OFFER));
+    ASSERT_EQ(1u, media_channel2_->send_streams().size());
+    EXPECT_EQ(stream2, media_channel2_->send_streams()[0]);
+
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content3, CA_OFFER));
+    ASSERT_EQ(1u, media_channel1_->recv_streams().size());
+    EXPECT_EQ(stream2, media_channel1_->recv_streams()[0]);
+
+    // Channel 1 replies but stop sending stream1.
+    typename T::Content content4;
+    CreateContent(SECURE, kPcmuCodec, kH264Codec, &content4);
+    EXPECT_TRUE(channel1_->SetLocalContent(&content4, CA_ANSWER));
+    EXPECT_EQ(0u, media_channel1_->send_streams().size());
+
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content4, CA_ANSWER));
+    EXPECT_EQ(0u, media_channel2_->recv_streams().size());
+
+    EXPECT_TRUE(channel1_->secure());
+    EXPECT_TRUE(channel2_->secure());
+    EXPECT_TRUE(SendCustomRtp2(kSsrc2));
+    EXPECT_TRUE(CheckCustomRtp1(kSsrc2));
+  }
+
+  // Test that we only start playout and sending at the right times.
+  void TestPlayoutAndSendingStates() {
+    CreateChannels(0, 0);
+    EXPECT_FALSE(media_channel1_->playout());
+    EXPECT_FALSE(media_channel1_->sending());
+    EXPECT_FALSE(media_channel2_->playout());
+    EXPECT_FALSE(media_channel2_->sending());
+    EXPECT_TRUE(channel1_->Enable(true));
+    EXPECT_FALSE(media_channel1_->playout());
+    EXPECT_FALSE(media_channel1_->sending());
+    EXPECT_TRUE(channel1_->SetLocalContent(&local_media_content1_, CA_OFFER));
+    EXPECT_TRUE(media_channel1_->playout());
+    EXPECT_FALSE(media_channel1_->sending());
+    EXPECT_TRUE(channel2_->SetRemoteContent(&local_media_content1_, CA_OFFER));
+    EXPECT_FALSE(media_channel2_->playout());
+    EXPECT_FALSE(media_channel2_->sending());
+    EXPECT_TRUE(channel2_->SetLocalContent(&local_media_content2_, CA_ANSWER));
+    EXPECT_FALSE(media_channel2_->playout());
+    EXPECT_FALSE(media_channel2_->sending());
+    session1_.Connect(&session2_);
+    EXPECT_TRUE(media_channel1_->playout());
+    EXPECT_FALSE(media_channel1_->sending());
+    EXPECT_FALSE(media_channel2_->playout());
+    EXPECT_FALSE(media_channel2_->sending());
+    EXPECT_TRUE(channel2_->Enable(true));
+    EXPECT_TRUE(media_channel2_->playout());
+    EXPECT_TRUE(media_channel2_->sending());
+    EXPECT_TRUE(channel1_->SetRemoteContent(&local_media_content2_, CA_ANSWER));
+    EXPECT_TRUE(media_channel1_->playout());
+    EXPECT_TRUE(media_channel1_->sending());
+  }
+
+  // Test setting up a call.
+  void TestCallSetup() {
+    CreateChannels(0, 0);
+    EXPECT_FALSE(channel1_->secure());
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(media_channel1_->playout());
+    EXPECT_FALSE(media_channel1_->sending());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_FALSE(channel1_->secure());
+    EXPECT_TRUE(media_channel1_->sending());
+    EXPECT_EQ(1U, media_channel1_->codecs().size());
+    EXPECT_TRUE(media_channel2_->playout());
+    EXPECT_TRUE(media_channel2_->sending());
+    EXPECT_EQ(1U, media_channel2_->codecs().size());
+  }
+
+  // Test that we don't crash if packets are sent during call teardown
+  // when RTCP mux is enabled. This is a regression test against a specific
+  // race condition that would only occur when a RTCP packet was sent during
+  // teardown of a channel on which RTCP mux was enabled.
+  void TestCallTeardownRtcpMux() {
+    class LastWordMediaChannel : public T::MediaChannel {
+     public:
+      LastWordMediaChannel() : T::MediaChannel(NULL) {}
+      ~LastWordMediaChannel() {
+        T::MediaChannel::SendRtp(kPcmuFrame, sizeof(kPcmuFrame));
+        T::MediaChannel::SendRtcp(kRtcpReport, sizeof(kRtcpReport));
+      }
+    };
+    CreateChannels(new LastWordMediaChannel(), new LastWordMediaChannel(),
+                   RTCP | RTCP_MUX, RTCP | RTCP_MUX,
+                   talk_base::Thread::Current());
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_TRUE(SendTerminate());
+  }
+
+  // Send voice RTP data to the other side and ensure it gets there.
+  void SendRtpToRtp() {
+    CreateChannels(0, 0);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(1U, GetTransport1()->channels().size());
+    EXPECT_EQ(1U, GetTransport2()->channels().size());
+    EXPECT_TRUE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckRtp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+  }
+
+  // Check that RTCP is not transmitted if both sides don't support RTCP.
+  void SendNoRtcpToNoRtcp() {
+    CreateChannels(0, 0);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(1U, GetTransport1()->channels().size());
+    EXPECT_EQ(1U, GetTransport2()->channels().size());
+    EXPECT_FALSE(SendRtcp1());
+    EXPECT_FALSE(SendRtcp2());
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Check that RTCP is not transmitted if the callee doesn't support RTCP.
+  void SendNoRtcpToRtcp() {
+    CreateChannels(0, RTCP);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(1U, GetTransport1()->channels().size());
+    EXPECT_EQ(2U, GetTransport2()->channels().size());
+    EXPECT_FALSE(SendRtcp1());
+    EXPECT_FALSE(SendRtcp2());
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Check that RTCP is not transmitted if the caller doesn't support RTCP.
+  void SendRtcpToNoRtcp() {
+    CreateChannels(RTCP, 0);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_EQ(1U, GetTransport2()->channels().size());
+    EXPECT_FALSE(SendRtcp1());
+    EXPECT_FALSE(SendRtcp2());
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Check that RTCP is transmitted if both sides support RTCP.
+  void SendRtcpToRtcp() {
+    CreateChannels(RTCP, RTCP);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_EQ(2U, GetTransport2()->channels().size());
+    EXPECT_TRUE(SendRtcp1());
+    EXPECT_TRUE(SendRtcp2());
+    EXPECT_TRUE(CheckRtcp1());
+    EXPECT_TRUE(CheckRtcp2());
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Check that RTCP is transmitted if only the initiator supports mux.
+  void SendRtcpMuxToRtcp() {
+    CreateChannels(RTCP | RTCP_MUX, RTCP);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_EQ(2U, GetTransport2()->channels().size());
+    EXPECT_TRUE(SendRtcp1());
+    EXPECT_TRUE(SendRtcp2());
+    EXPECT_TRUE(CheckRtcp1());
+    EXPECT_TRUE(CheckRtcp2());
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Check that RTP and RTCP are transmitted ok when both sides support mux.
+  void SendRtcpMuxToRtcpMux() {
+    CreateChannels(RTCP | RTCP_MUX, RTCP | RTCP_MUX);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_EQ(1U, GetTransport2()->channels().size());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(1U, GetTransport1()->channels().size());
+    EXPECT_TRUE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(SendRtcp1());
+    EXPECT_TRUE(SendRtcp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckRtp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+    EXPECT_TRUE(CheckRtcp1());
+    EXPECT_TRUE(CheckRtcp2());
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Check that RTCP data sent by the initiator before the accept is not muxed.
+  void SendEarlyRtcpMuxToRtcp() {
+    CreateChannels(RTCP | RTCP_MUX, RTCP);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_EQ(2U, GetTransport2()->channels().size());
+
+    // RTCP can be sent before the call is accepted, if the transport is ready.
+    // It should not be muxed though, as the remote side doesn't support mux.
+    EXPECT_TRUE(SendRtcp1());
+    EXPECT_TRUE(CheckNoRtp2());
+    EXPECT_TRUE(CheckRtcp2());
+
+    // Send RTCP packet from callee and verify that it is received.
+    EXPECT_TRUE(SendRtcp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckRtcp1());
+
+    // Complete call setup and ensure everything is still OK.
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_TRUE(SendRtcp1());
+    EXPECT_TRUE(CheckRtcp2());
+    EXPECT_TRUE(SendRtcp2());
+    EXPECT_TRUE(CheckRtcp1());
+  }
+
+
+  // Check that RTCP data is not muxed until both sides have enabled muxing,
+  // but that we properly demux before we get the accept message, since there
+  // is a race between RTP data and the jingle accept.
+  void SendEarlyRtcpMuxToRtcpMux() {
+    CreateChannels(RTCP | RTCP_MUX, RTCP | RTCP_MUX);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_EQ(1U, GetTransport2()->channels().size());
+
+    // RTCP can't be sent yet, since the RTCP transport isn't writable, and
+    // we haven't yet received the accept that says we should mux.
+    EXPECT_FALSE(SendRtcp1());
+
+    // Send muxed RTCP packet from callee and verify that it is received.
+    EXPECT_TRUE(SendRtcp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckRtcp1());
+
+    // Complete call setup and ensure everything is still OK.
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(1U, GetTransport1()->channels().size());
+    EXPECT_TRUE(SendRtcp1());
+    EXPECT_TRUE(CheckRtcp2());
+    EXPECT_TRUE(SendRtcp2());
+    EXPECT_TRUE(CheckRtcp1());
+  }
+
+  // Test that we properly send SRTP with RTCP in both directions.
+  void SendSrtpToSrtp() {
+    CreateChannels(RTCP | SECURE, RTCP | SECURE);
+    EXPECT_FALSE(channel1_->secure());
+    EXPECT_FALSE(channel2_->secure());
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_TRUE(channel1_->secure());
+    EXPECT_TRUE(channel2_->secure());
+    EXPECT_TRUE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(SendRtcp1());
+    EXPECT_TRUE(SendRtcp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckRtp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+    EXPECT_TRUE(CheckRtcp1());
+    EXPECT_TRUE(CheckRtcp2());
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Test that we properly handling SRTP negotiating down to RTP.
+  void SendSrtpToRtp() {
+    CreateChannels(RTCP | SECURE, RTCP);
+    EXPECT_FALSE(channel1_->secure());
+    EXPECT_FALSE(channel2_->secure());
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_FALSE(channel1_->secure());
+    EXPECT_FALSE(channel2_->secure());
+    EXPECT_TRUE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(SendRtcp1());
+    EXPECT_TRUE(SendRtcp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckRtp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+    EXPECT_TRUE(CheckRtcp1());
+    EXPECT_TRUE(CheckRtcp2());
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Test that we properly send SRTP with RTCP mux in both directions.
+  void SendSrtcpMux() {
+    CreateChannels(RTCP | RTCP_MUX  | SECURE, RTCP | RTCP_MUX | SECURE);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_TRUE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(SendRtcp1());
+    EXPECT_TRUE(SendRtcp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckRtp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+    EXPECT_TRUE(CheckRtcp1());
+    EXPECT_TRUE(CheckRtcp2());
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Test that we properly send RTP without SRTP from a thread.
+  void SendRtpToRtpOnThread() {
+    bool sent_rtp1, sent_rtp2, sent_rtcp1, sent_rtcp2;
+    CreateChannels(RTCP, RTCP);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    CallOnThread(&ChannelTest<T>::SendRtp1, &sent_rtp1);
+    CallOnThread(&ChannelTest<T>::SendRtp2, &sent_rtp2);
+    CallOnThread(&ChannelTest<T>::SendRtcp1, &sent_rtcp1);
+    CallOnThread(&ChannelTest<T>::SendRtcp2, &sent_rtcp2);
+    EXPECT_TRUE_WAIT(CheckRtp1(), 1000);
+    EXPECT_TRUE_WAIT(CheckRtp2(), 1000);
+    EXPECT_TRUE_WAIT(sent_rtp1, 1000);
+    EXPECT_TRUE_WAIT(sent_rtp2, 1000);
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+    EXPECT_TRUE_WAIT(CheckRtcp1(), 1000);
+    EXPECT_TRUE_WAIT(CheckRtcp2(), 1000);
+    EXPECT_TRUE_WAIT(sent_rtcp1, 1000);
+    EXPECT_TRUE_WAIT(sent_rtcp2, 1000);
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Test that we properly send SRTP with RTCP from a thread.
+  void SendSrtpToSrtpOnThread() {
+    bool sent_rtp1, sent_rtp2, sent_rtcp1, sent_rtcp2;
+    CreateChannels(RTCP | SECURE, RTCP | SECURE);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    CallOnThread(&ChannelTest<T>::SendRtp1, &sent_rtp1);
+    CallOnThread(&ChannelTest<T>::SendRtp2, &sent_rtp2);
+    CallOnThread(&ChannelTest<T>::SendRtcp1, &sent_rtcp1);
+    CallOnThread(&ChannelTest<T>::SendRtcp2, &sent_rtcp2);
+    EXPECT_TRUE_WAIT(CheckRtp1(), 1000);
+    EXPECT_TRUE_WAIT(CheckRtp2(), 1000);
+    EXPECT_TRUE_WAIT(sent_rtp1, 1000);
+    EXPECT_TRUE_WAIT(sent_rtp2, 1000);
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+    EXPECT_TRUE_WAIT(CheckRtcp1(), 1000);
+    EXPECT_TRUE_WAIT(CheckRtcp2(), 1000);
+    EXPECT_TRUE_WAIT(sent_rtcp1, 1000);
+    EXPECT_TRUE_WAIT(sent_rtcp2, 1000);
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  // Test that the mediachannel retains its sending state after the transport
+  // becomes non-writable.
+  void SendWithWritabilityLoss() {
+    CreateChannels(0, 0);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(1U, GetTransport1()->channels().size());
+    EXPECT_EQ(1U, GetTransport2()->channels().size());
+    EXPECT_TRUE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckRtp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+
+    GetTransport1()->SetDestination(NULL);
+    EXPECT_TRUE(media_channel1_->sending());
+    EXPECT_FALSE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+
+    GetTransport1()->SetDestination(GetTransport2());
+    EXPECT_TRUE(media_channel1_->sending());
+    EXPECT_TRUE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckRtp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+  }
+
+  void SendSsrcMuxToSsrcMuxWithRtcpMux() {
+    CreateChannels(SSRC_MUX | RTCP | RTCP_MUX, SSRC_MUX | RTCP | RTCP_MUX);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_EQ(1U, GetTransport2()->channels().size());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(1U, GetTransport1()->channels().size());
+    EXPECT_EQ(1U, GetTransport2()->channels().size());
+    EXPECT_TRUE(channel1_->ssrc_filter()->IsActive());
+    // channel1 - should have media_content2 as remote. i.e. kSsrc2
+    EXPECT_TRUE(channel1_->ssrc_filter()->FindStream(kSsrc2));
+    EXPECT_TRUE(channel2_->ssrc_filter()->IsActive());
+    // channel2 - should have media_content1 as remote. i.e. kSsrc1
+    EXPECT_TRUE(channel2_->ssrc_filter()->FindStream(kSsrc1));
+    EXPECT_TRUE(SendCustomRtp1(kSsrc1));
+    EXPECT_TRUE(SendCustomRtp2(kSsrc2));
+    EXPECT_TRUE(SendCustomRtcp1(kSsrc1));
+    EXPECT_TRUE(SendCustomRtcp2(kSsrc2));
+    EXPECT_TRUE(CheckCustomRtp1(kSsrc2));
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckCustomRtp2(kSsrc1));
+    EXPECT_TRUE(CheckNoRtp2());
+    EXPECT_TRUE(CheckCustomRtcp1(kSsrc2));
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckCustomRtcp2(kSsrc1));
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  void SendSsrcMuxToSsrcMux() {
+    CreateChannels(SSRC_MUX | RTCP, SSRC_MUX | RTCP);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_EQ(2U, GetTransport2()->channels().size());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_EQ(2U, GetTransport2()->channels().size());
+    EXPECT_TRUE(channel1_->ssrc_filter()->IsActive());
+    // channel1 - should have media_content2 as remote. i.e. kSsrc2
+    EXPECT_TRUE(channel1_->ssrc_filter()->FindStream(kSsrc2));
+    EXPECT_TRUE(channel2_->ssrc_filter()->IsActive());
+    // channel2 - should have media_content1 as remote. i.e. kSsrc1
+    EXPECT_TRUE(SendCustomRtp1(kSsrc1));
+    EXPECT_TRUE(SendCustomRtp2(kSsrc2));
+    EXPECT_TRUE(SendCustomRtcp1(kSsrc1));
+    EXPECT_TRUE(SendCustomRtcp2(kSsrc2));
+    EXPECT_TRUE(CheckCustomRtp1(kSsrc2));
+    EXPECT_FALSE(CheckCustomRtp1(kSsrc1));
+    EXPECT_TRUE(CheckCustomRtp2(kSsrc1));
+    EXPECT_FALSE(CheckCustomRtp2(kSsrc2));
+    EXPECT_TRUE(CheckCustomRtcp1(kSsrc2));
+    EXPECT_FALSE(CheckCustomRtcp1(kSsrc1));
+    EXPECT_TRUE(CheckCustomRtcp2(kSsrc1));
+    EXPECT_FALSE(CheckCustomRtcp2(kSsrc2));
+  }
+
+  // Test that the media monitor can be run and gives timely callbacks.
+  void TestMediaMonitor() {
+    static const int kTimeout = 500;
+    CreateChannels(0, 0);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    channel1_->StartMediaMonitor(100);
+    channel2_->StartMediaMonitor(100);
+    // Ensure we get callbacks and stop.
+    EXPECT_TRUE_WAIT(media_info_callbacks1_ > 0, kTimeout);
+    EXPECT_TRUE_WAIT(media_info_callbacks2_ > 0, kTimeout);
+    channel1_->StopMediaMonitor();
+    channel2_->StopMediaMonitor();
+    // Ensure a restart of a stopped monitor works.
+    channel1_->StartMediaMonitor(100);
+    EXPECT_TRUE_WAIT(media_info_callbacks1_ > 0, kTimeout);
+    channel1_->StopMediaMonitor();
+    // Ensure stopping a stopped monitor is OK.
+    channel1_->StopMediaMonitor();
+  }
+
+  void TestMediaSinks() {
+    CreateChannels(0, 0);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_FALSE(channel1_->HasSendSinks());
+    EXPECT_FALSE(channel1_->HasRecvSinks());
+
+    talk_base::Pathname path;
+    EXPECT_TRUE(talk_base::Filesystem::GetTemporaryFolder(path, true, NULL));
+    path.SetFilename("sink-test.rtpdump");
+    talk_base::scoped_ptr<cricket::RtpDumpSink> sink(
+        new cricket::RtpDumpSink(path.pathname()));
+    sink->set_packet_filter(cricket::PF_ALL);
+    EXPECT_TRUE(sink->Enable(true));
+    channel1_->RegisterSendSink(sink.get(), &cricket::RtpDumpSink::OnPacket);
+    EXPECT_TRUE(channel1_->HasSendSinks());
+    EXPECT_FALSE(channel1_->HasRecvSinks());
+    // The first packet is recorded with header + data.
+    EXPECT_TRUE(SendRtp1());
+    // The second packet is recorded with header only.
+    sink->set_packet_filter(cricket::PF_RTPHEADER);
+    EXPECT_TRUE(SendRtp1());
+    // The third packet is not recorded since sink is disabled.
+    EXPECT_TRUE(sink->Enable(false));
+    EXPECT_TRUE(SendRtp1());
+     // The fourth packet is not recorded since sink is unregistered.
+    EXPECT_TRUE(sink->Enable(true));
+    channel1_->UnregisterSendSink(sink.get());
+    EXPECT_TRUE(SendRtp1());
+    sink.reset();  // This will close the file.
+
+    // Read the recorded file and verify two packets.
+    talk_base::scoped_ptr<talk_base::StreamInterface> stream(
+        talk_base::Filesystem::OpenFile(path, "rb"));
+
+    cricket::RtpDumpReader reader(stream.get());
+    cricket::RtpDumpPacket packet;
+    EXPECT_EQ(talk_base::SR_SUCCESS, reader.ReadPacket(&packet));
+    std::string read_packet(reinterpret_cast<const char*>(&packet.data[0]),
+        packet.data.size());
+    EXPECT_EQ(rtp_packet_, read_packet);
+
+    EXPECT_EQ(talk_base::SR_SUCCESS, reader.ReadPacket(&packet));
+    size_t len = 0;
+    packet.GetRtpHeaderLen(&len);
+    EXPECT_EQ(len, packet.data.size());
+    EXPECT_EQ(0, memcmp(&packet.data[0], rtp_packet_.c_str(), len));
+
+    EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+
+    // Delete the file for media recording.
+    stream.reset();
+    EXPECT_TRUE(talk_base::Filesystem::DeleteFile(path));
+  }
+
+  void TestSetContentFailure() {
+    CreateChannels(0, 0);
+    typename T::Content content;
+    cricket::SessionDescription* sdesc_loc = new cricket::SessionDescription();
+    cricket::SessionDescription* sdesc_rem = new cricket::SessionDescription();
+
+    // Set up the session description.
+    CreateContent(0, kPcmuCodec, kH264Codec, &content);
+    sdesc_loc->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+                          new cricket::AudioContentDescription());
+    sdesc_loc->AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+                          new cricket::VideoContentDescription());
+    EXPECT_TRUE(session1_.set_local_description(sdesc_loc));
+    sdesc_rem->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+                          new cricket::AudioContentDescription());
+    sdesc_rem->AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+                          new cricket::VideoContentDescription());
+    EXPECT_TRUE(session1_.set_remote_description(sdesc_rem));
+
+    // Test failures in SetLocalContent.
+    media_channel1_->set_fail_set_recv_codecs(true);
+    session1_.SetError(cricket::BaseSession::ERROR_NONE);
+    session1_.SignalState(&session1_, cricket::Session::STATE_SENTINITIATE);
+    EXPECT_EQ(cricket::BaseSession::ERROR_CONTENT, session1_.error());
+    media_channel1_->set_fail_set_recv_codecs(true);
+    session1_.SetError(cricket::BaseSession::ERROR_NONE);
+    session1_.SignalState(&session1_, cricket::Session::STATE_SENTACCEPT);
+    EXPECT_EQ(cricket::BaseSession::ERROR_CONTENT, session1_.error());
+
+    // Test failures in SetRemoteContent.
+    media_channel1_->set_fail_set_send_codecs(true);
+    session1_.SetError(cricket::BaseSession::ERROR_NONE);
+    session1_.SignalState(&session1_, cricket::Session::STATE_RECEIVEDINITIATE);
+    EXPECT_EQ(cricket::BaseSession::ERROR_CONTENT, session1_.error());
+    media_channel1_->set_fail_set_send_codecs(true);
+    session1_.SetError(cricket::BaseSession::ERROR_NONE);
+    session1_.SignalState(&session1_, cricket::Session::STATE_RECEIVEDACCEPT);
+    EXPECT_EQ(cricket::BaseSession::ERROR_CONTENT, session1_.error());
+  }
+
+  void TestFlushRtcp() {
+    bool send_rtcp1;
+
+    CreateChannels(RTCP, RTCP);
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_EQ(2U, GetTransport1()->channels().size());
+    EXPECT_EQ(2U, GetTransport2()->channels().size());
+
+    // Send RTCP1 from a different thread.
+    CallOnThreadAndWaitForDone(&ChannelTest<T>::SendRtcp1, &send_rtcp1);
+    EXPECT_TRUE(send_rtcp1);
+    // The sending message is only posted.  channel2_ should be empty.
+    EXPECT_TRUE(CheckNoRtcp2());
+
+    // When channel1_ is deleted, the RTCP packet should be sent out to
+    // channel2_.
+    channel1_.reset();
+    EXPECT_TRUE(CheckRtcp2());
+  }
+
+  void TestChangeStateError() {
+    CreateChannels(RTCP, RTCP);
+    EXPECT_TRUE(SendInitiate());
+    media_channel2_->set_fail_set_send(true);
+    EXPECT_TRUE(channel2_->Enable(true));
+    EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_DEVICE_OPEN_FAILED,
+              error_);
+  }
+
+  void TestSrtpError() {
+    static const unsigned char kBadPacket[] = {
+      0x90, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+    };
+    CreateChannels(RTCP | SECURE, RTCP | SECURE);
+    EXPECT_FALSE(channel1_->secure());
+    EXPECT_FALSE(channel2_->secure());
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE(SendAccept());
+    EXPECT_TRUE(channel1_->secure());
+    EXPECT_TRUE(channel2_->secure());
+    channel2_->set_srtp_signal_silent_time(200);
+
+    // Testing failures in sending packets.
+    EXPECT_FALSE(media_channel2_->SendRtp(kBadPacket, sizeof(kBadPacket)));
+    // The first failure will trigger an error.
+    EXPECT_EQ_WAIT(T::MediaChannel::ERROR_REC_SRTP_ERROR, error_, 500);
+    error_ = T::MediaChannel::ERROR_NONE;
+    // The next 1 sec failures will not trigger an error.
+    EXPECT_FALSE(media_channel2_->SendRtp(kBadPacket, sizeof(kBadPacket)));
+    // Wait for a while to ensure no message comes in.
+    talk_base::Thread::Current()->ProcessMessages(210);
+    EXPECT_EQ(T::MediaChannel::ERROR_NONE, error_);
+    // The error will be triggered again.
+    EXPECT_FALSE(media_channel2_->SendRtp(kBadPacket, sizeof(kBadPacket)));
+    EXPECT_EQ_WAIT(T::MediaChannel::ERROR_REC_SRTP_ERROR, error_, 500);
+
+    // Testing failures in receiving packets.
+    error_ = T::MediaChannel::ERROR_NONE;
+    cricket::TransportChannel* transport_channel =
+        channel2_->transport_channel();
+    transport_channel->SignalReadPacket(
+        transport_channel, reinterpret_cast<const char*>(kBadPacket),
+        sizeof(kBadPacket));
+    EXPECT_EQ_WAIT(T::MediaChannel::ERROR_PLAY_SRTP_AUTH_FAILED, error_, 500);
+  }
+
+ protected:
+  cricket::FakeSession session1_;
+  cricket::FakeSession session2_;
+  cricket::FakeMediaEngine media_engine_;
+  // The media channels are owned by the voice channel objects below.
+  typename T::MediaChannel* media_channel1_;
+  typename T::MediaChannel* media_channel2_;
+  talk_base::scoped_ptr<typename T::Channel> channel1_;
+  talk_base::scoped_ptr<typename T::Channel> channel2_;
+  typename T::Content local_media_content1_;
+  typename T::Content local_media_content2_;
+  typename T::Content remote_media_content1_;
+  typename T::Content remote_media_content2_;
+  // The RTP and RTCP packets to send in the tests.
+  std::string rtp_packet_;
+  std::string rtcp_packet_;
+  int media_info_callbacks1_;
+  int media_info_callbacks2_;
+  uint32 ssrc_;
+  typename T::MediaChannel::Error error_;
+};
+
+
+template<>
+void ChannelTest<VoiceTraits>::CreateContent(
+    int flags,
+    const cricket::AudioCodec& audio_codec,
+    const cricket::VideoCodec& video_codec,
+    cricket::AudioContentDescription* audio) {
+  audio->AddCodec(audio_codec);
+  audio->set_rtcp_mux((flags & RTCP_MUX) != 0);
+  if (flags & SECURE) {
+    audio->AddCrypto(cricket::CryptoParams(
+        1, cricket::CS_AES_CM_128_HMAC_SHA1_32,
+        "inline:" + talk_base::CreateRandomString(40), ""));
+  }
+}
+
+template<>
+void ChannelTest<VoiceTraits>::CopyContent(
+    const cricket::AudioContentDescription& source,
+    cricket::AudioContentDescription* audio) {
+  *audio = source;
+}
+
+template<>
+bool ChannelTest<VoiceTraits>::CodecMatches(const cricket::AudioCodec& c1,
+                                            const cricket::AudioCodec& c2) {
+  return c1.name == c2.name && c1.clockrate == c2.clockrate &&
+      c1.bitrate == c2.bitrate && c1.channels == c2.channels;
+}
+
+template<>
+void ChannelTest<VoiceTraits>::AddLegacyStreamInContent(
+    uint32 ssrc, int flags, cricket::AudioContentDescription* audio) {
+  audio->AddLegacyStream(ssrc);
+}
+
+class VoiceChannelTest
+    : public ChannelTest<VoiceTraits> {
+ public:
+  typedef ChannelTest<VoiceTraits>
+  Base;
+  VoiceChannelTest() : Base(kPcmuFrame, sizeof(kPcmuFrame),
+                            kRtcpReport, sizeof(kRtcpReport)) {
+  }
+};
+
+// override to add NULL parameter
+template<>
+cricket::VideoChannel* ChannelTest<VideoTraits>::CreateChannel(
+    talk_base::Thread* thread, cricket::MediaEngineInterface* engine,
+    cricket::FakeVideoMediaChannel* ch, cricket::BaseSession* session,
+    bool rtcp) {
+  cricket::VideoChannel* channel = new cricket::VideoChannel(
+      thread, engine, ch, session, cricket::CN_VIDEO, rtcp, NULL);
+  if (!channel->Init()) {
+    delete channel;
+    channel = NULL;
+  }
+  return channel;
+}
+
+// override to add 0 parameter
+template<>
+bool ChannelTest<VideoTraits>::AddStream1(int id) {
+  return channel1_->AddRecvStream(cricket::StreamParams::CreateLegacy(id));
+}
+
+template<>
+void ChannelTest<VideoTraits>::CreateContent(
+    int flags,
+    const cricket::AudioCodec& audio_codec,
+    const cricket::VideoCodec& video_codec,
+    cricket::VideoContentDescription* video) {
+  video->AddCodec(video_codec);
+  video->set_rtcp_mux((flags & RTCP_MUX) != 0);
+  if (flags & SECURE) {
+    video->AddCrypto(cricket::CryptoParams(
+        1, cricket::CS_AES_CM_128_HMAC_SHA1_80,
+        "inline:" + talk_base::CreateRandomString(40), ""));
+  }
+}
+
+template<>
+void ChannelTest<VideoTraits>::CopyContent(
+    const cricket::VideoContentDescription& source,
+    cricket::VideoContentDescription* video) {
+  *video = source;
+}
+
+template<>
+bool ChannelTest<VideoTraits>::CodecMatches(const cricket::VideoCodec& c1,
+                                            const cricket::VideoCodec& c2) {
+  return c1.name == c2.name && c1.width == c2.width && c1.height == c2.height &&
+      c1.framerate == c2.framerate;
+}
+
+template<>
+void ChannelTest<VideoTraits>::AddLegacyStreamInContent(
+    uint32 ssrc, int flags, cricket::VideoContentDescription* video) {
+  video->AddLegacyStream(ssrc);
+}
+
+class VideoChannelTest
+    : public ChannelTest<VideoTraits> {
+ public:
+  typedef ChannelTest<VideoTraits>
+  Base;
+  VideoChannelTest() : Base(kH264Packet, sizeof(kH264Packet),
+                            kRtcpReport, sizeof(kRtcpReport)) {
+  }
+};
+
+
+// VoiceChannelTest
+
+TEST_F(VoiceChannelTest, TestInit) {
+  Base::TestInit();
+  EXPECT_FALSE(media_channel1_->muted());
+  EXPECT_TRUE(media_channel1_->dtmf_queue().empty());
+}
+
+TEST_F(VoiceChannelTest, TestSetContents) {
+  Base::TestSetContents();
+}
+
+TEST_F(VoiceChannelTest, TestSetContentsNullOffer) {
+  Base::TestSetContentsNullOffer();
+}
+
+TEST_F(VoiceChannelTest, TestSetContentsRtcpMux) {
+  Base::TestSetContentsRtcpMux();
+}
+
+TEST_F(VoiceChannelTest, TestSetRemoteContentUpdate) {
+  Base::TestSetRemoteContentUpdate();
+}
+
+TEST_F(VoiceChannelTest, TestStreams) {
+  Base::TestStreams();
+}
+
+TEST_F(VoiceChannelTest, TestUpdateStreamsInLocalContent) {
+  Base::TestUpdateStreamsInLocalContent();
+}
+
+TEST_F(VoiceChannelTest, TestUpdateRemoteStreamsInContent) {
+  Base::TestUpdateStreamsInRemoteContent();
+}
+
+TEST_F(VoiceChannelTest, TestChangeStreamParamsInContent) {
+  Base::TestChangeStreamParamsInContent();
+}
+
+TEST_F(VoiceChannelTest, TestPlayoutAndSendingStates) {
+  Base::TestPlayoutAndSendingStates();
+}
+
+TEST_F(VoiceChannelTest, TestCallSetup) {
+  Base::TestCallSetup();
+}
+
+TEST_F(VoiceChannelTest, TestCallTeardownRtcpMux) {
+  Base::TestCallTeardownRtcpMux();
+}
+
+TEST_F(VoiceChannelTest, SendRtpToRtp) {
+  Base::SendRtpToRtp();
+}
+
+TEST_F(VoiceChannelTest, SendNoRtcpToNoRtcp) {
+  Base::SendNoRtcpToNoRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendNoRtcpToRtcp) {
+  Base::SendNoRtcpToRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendRtcpToNoRtcp) {
+  Base::SendRtcpToNoRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendRtcpToRtcp) {
+  Base::SendRtcpToRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendRtcpMuxToRtcp) {
+  Base::SendRtcpMuxToRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendRtcpMuxToRtcpMux) {
+  Base::SendRtcpMuxToRtcpMux();
+}
+
+TEST_F(VoiceChannelTest, SendEarlyRtcpMuxToRtcp) {
+  Base::SendEarlyRtcpMuxToRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendEarlyRtcpMuxToRtcpMux) {
+  Base::SendEarlyRtcpMuxToRtcpMux();
+}
+
+TEST_F(VoiceChannelTest, SendSrtpToSrtp) {
+  Base::SendSrtpToSrtp();
+}
+
+TEST_F(VoiceChannelTest, SendSrtpToRtp) {
+  Base::SendSrtpToSrtp();
+}
+
+TEST_F(VoiceChannelTest, SendSrtcpMux) {
+  Base::SendSrtcpMux();
+}
+
+TEST_F(VoiceChannelTest, SendRtpToRtpOnThread) {
+  Base::SendRtpToRtpOnThread();
+}
+
+TEST_F(VoiceChannelTest, SendSrtpToSrtpOnThread) {
+  Base::SendSrtpToSrtpOnThread();
+}
+
+TEST_F(VoiceChannelTest, SendWithWritabilityLoss) {
+  Base::SendWithWritabilityLoss();
+}
+
+TEST_F(VoiceChannelTest, TestMediaMonitor) {
+  Base::TestMediaMonitor();
+}
+
+// Test that Mute properly forwards to the media channel.
+TEST_F(VoiceChannelTest, TestMute) {
+  CreateChannels(0, 0);
+  EXPECT_FALSE(media_channel1_->muted());
+  EXPECT_TRUE(channel1_->Mute(true));
+  EXPECT_TRUE(media_channel1_->muted());
+  EXPECT_TRUE(channel1_->Mute(false));
+  EXPECT_FALSE(media_channel1_->muted());
+}
+
+// Test that keyboard automute works correctly.
+TEST_F(VoiceChannelTest, TestKeyboardMute) {
+  CreateChannels(0, 0);
+  EXPECT_FALSE(media_channel1_->muted());
+  EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_NONE, error_);
+
+  cricket::VoiceMediaChannel::Error e =
+      cricket::VoiceMediaChannel::ERROR_REC_TYPING_NOISE_DETECTED;
+
+  // Typing doesn't mute automatically
+  media_channel1_->TriggerError(0, e);
+  talk_base::Thread::Current()->ProcessMessages(0);
+  EXPECT_EQ(e, error_);
+  EXPECT_FALSE(media_channel1_->muted());
+
+  // But it does when enabled
+  channel1_->set_mute_on_type(true, 200);
+  media_channel1_->TriggerError(0, e);
+  error_ = cricket::VoiceMediaChannel::ERROR_NONE;
+  EXPECT_TRUE_WAIT(error_ == e, 100);
+  EXPECT_TRUE(media_channel1_->muted());
+  EXPECT_TRUE_WAIT(!media_channel1_->muted(), 250);  // And resets.
+
+  // Muting manually preemts auto-unmute
+  media_channel1_->TriggerError(0, e);
+  error_ = cricket::VoiceMediaChannel::ERROR_NONE;
+  EXPECT_TRUE_WAIT(error_ == e, 100);
+  EXPECT_TRUE(media_channel1_->muted());
+  EXPECT_TRUE(channel1_->Mute(true));
+  talk_base::Thread::Current()->ProcessMessages(250);
+  EXPECT_TRUE(media_channel1_->muted());
+}
+
+// Test that PressDTMF properly forwards to the media channel.
+TEST_F(VoiceChannelTest, TestDtmf) {
+  CreateChannels(0, 0);
+  EXPECT_TRUE(SendInitiate());
+  EXPECT_TRUE(SendAccept());
+  EXPECT_EQ(0U, media_channel1_->dtmf_queue().size());
+  EXPECT_TRUE(channel1_->PressDTMF(1, true));
+  EXPECT_TRUE(channel1_->PressDTMF(8, false));
+  ASSERT_EQ(2U, media_channel1_->dtmf_queue().size());
+  EXPECT_EQ(1, media_channel1_->dtmf_queue()[0].first);
+  EXPECT_EQ(true, media_channel1_->dtmf_queue()[0].second);
+  EXPECT_EQ(8, media_channel1_->dtmf_queue()[1].first);
+  EXPECT_FALSE(media_channel1_->dtmf_queue()[1].second);
+}
+
+TEST_F(VoiceChannelTest, TestMediaSinks) {
+  Base::TestMediaSinks();
+}
+
+TEST_F(VoiceChannelTest, TestSetContentFailure) {
+  Base::TestSetContentFailure();
+}
+
+TEST_F(VoiceChannelTest, TestFlushRtcp) {
+  Base::TestFlushRtcp();
+}
+
+TEST_F(VoiceChannelTest, TestChangeStateError) {
+  Base::TestChangeStateError();
+}
+
+TEST_F(VoiceChannelTest, TestSrtpError) {
+  Base::TestSrtpError();
+}
+
+// Test that we can play a ringback tone properly.
+TEST_F(VoiceChannelTest, TestRingbackTone) {
+  CreateChannels(RTCP, RTCP);
+  EXPECT_FALSE(media_channel1_->ringback_tone_play());
+  EXPECT_TRUE(channel1_->SetRingbackTone("RIFF", 4));
+  EXPECT_TRUE(SendInitiate());
+  EXPECT_TRUE(SendAccept());
+  // Play ringback tone, no loop.
+  EXPECT_TRUE(channel1_->PlayRingbackTone(0, true, false));
+  EXPECT_EQ(0U, media_channel1_->ringback_tone_ssrc());
+  EXPECT_TRUE(media_channel1_->ringback_tone_play());
+  EXPECT_FALSE(media_channel1_->ringback_tone_loop());
+  // Stop the ringback tone.
+  EXPECT_TRUE(channel1_->PlayRingbackTone(0, false, false));
+  EXPECT_FALSE(media_channel1_->ringback_tone_play());
+  // Add a stream.
+  EXPECT_TRUE(AddStream1(1));
+  // Play ringback tone, looping, on the new stream.
+  EXPECT_TRUE(channel1_->PlayRingbackTone(1, true, true));
+  EXPECT_EQ(1U, media_channel1_->ringback_tone_ssrc());
+  EXPECT_TRUE(media_channel1_->ringback_tone_play());
+  EXPECT_TRUE(media_channel1_->ringback_tone_loop());
+  // Stop the ringback tone.
+  EXPECT_TRUE(channel1_->PlayRingbackTone(1, false, false));
+  EXPECT_FALSE(media_channel1_->ringback_tone_play());
+}
+
+// Test that we can scale the output volume properly for 1:1 calls.
+TEST_F(VoiceChannelTest, TestScaleVolume1to1Call) {
+  CreateChannels(RTCP, RTCP);
+  EXPECT_TRUE(SendInitiate());
+  EXPECT_TRUE(SendAccept());
+  double left, right;
+
+  // Default is (1.0, 1.0).
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+  EXPECT_DOUBLE_EQ(1.0, left);
+  EXPECT_DOUBLE_EQ(1.0, right);
+  // invalid ssrc.
+  EXPECT_FALSE(media_channel1_->GetOutputScaling(3, &left, &right));
+
+  // Set scale to (1.5, 0.5).
+  EXPECT_TRUE(channel1_->SetOutputScaling(0, 1.5, 0.5));
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+  EXPECT_DOUBLE_EQ(1.5, left);
+  EXPECT_DOUBLE_EQ(0.5, right);
+
+  // Set scale to (0, 0).
+  EXPECT_TRUE(channel1_->SetOutputScaling(0, 0.0, 0.0));
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+  EXPECT_DOUBLE_EQ(0.0, left);
+  EXPECT_DOUBLE_EQ(0.0, right);
+}
+
+// Test that we can scale the output volume properly for multiway calls.
+TEST_F(VoiceChannelTest, TestScaleVolumeMultiwayCall) {
+  CreateChannels(RTCP, RTCP);
+  EXPECT_TRUE(SendInitiate());
+  EXPECT_TRUE(SendAccept());
+  EXPECT_TRUE(AddStream1(1));
+  EXPECT_TRUE(AddStream1(2));
+
+  double left, right;
+  // Default is (1.0, 1.0).
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+  EXPECT_DOUBLE_EQ(1.0, left);
+  EXPECT_DOUBLE_EQ(1.0, right);
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(1, &left, &right));
+  EXPECT_DOUBLE_EQ(1.0, left);
+  EXPECT_DOUBLE_EQ(1.0, right);
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(2, &left, &right));
+  EXPECT_DOUBLE_EQ(1.0, left);
+  EXPECT_DOUBLE_EQ(1.0, right);
+  // invalid ssrc.
+  EXPECT_FALSE(media_channel1_->GetOutputScaling(3, &left, &right));
+
+  // Set scale to (1.5, 0.5) for ssrc = 1.
+  EXPECT_TRUE(channel1_->SetOutputScaling(1, 1.5, 0.5));
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(1, &left, &right));
+  EXPECT_DOUBLE_EQ(1.5, left);
+  EXPECT_DOUBLE_EQ(0.5, right);
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(2, &left, &right));
+  EXPECT_DOUBLE_EQ(1.0, left);
+  EXPECT_DOUBLE_EQ(1.0, right);
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+  EXPECT_DOUBLE_EQ(1.0, left);
+  EXPECT_DOUBLE_EQ(1.0, right);
+
+  // Set scale to (0, 0) for all ssrcs.
+  EXPECT_TRUE(channel1_->SetOutputScaling(0,  0.0, 0.0));
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+  EXPECT_DOUBLE_EQ(0.0, left);
+  EXPECT_DOUBLE_EQ(0.0, right);
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(1, &left, &right));
+  EXPECT_DOUBLE_EQ(0.0, left);
+  EXPECT_DOUBLE_EQ(0.0, right);
+  EXPECT_TRUE(media_channel1_->GetOutputScaling(2, &left, &right));
+  EXPECT_DOUBLE_EQ(0.0, left);
+  EXPECT_DOUBLE_EQ(0.0, right);
+}
+
+TEST_F(VoiceChannelTest, SendSsrcMuxToSsrcMux) {
+  Base::SendSsrcMuxToSsrcMux();
+}
+
+TEST_F(VoiceChannelTest, SendSsrcMuxToSsrcMuxWithRtcpMux) {
+  Base::SendSsrcMuxToSsrcMuxWithRtcpMux();
+}
+
+// VideoChannelTest
+TEST_F(VideoChannelTest, TestInit) {
+  Base::TestInit();
+}
+
+TEST_F(VideoChannelTest, TestSetContents) {
+  Base::TestSetContents();
+}
+
+TEST_F(VideoChannelTest, TestSetContentsNullOffer) {
+  Base::TestSetContentsNullOffer();
+}
+
+TEST_F(VideoChannelTest, TestSetContentsRtcpMux) {
+  Base::TestSetContentsRtcpMux();
+}
+
+TEST_F(VideoChannelTest, TestSetRemoteContentUpdate) {
+  Base::TestSetRemoteContentUpdate();
+}
+
+TEST_F(VideoChannelTest, TestStreams) {
+  Base::TestStreams();
+}
+
+TEST_F(VideoChannelTest, TestUpdateStreamsInLocalContent) {
+  Base::TestUpdateStreamsInLocalContent();
+}
+
+TEST_F(VideoChannelTest, TestUpdateRemoteStreamsInContent) {
+  Base::TestUpdateStreamsInRemoteContent();
+}
+
+TEST_F(VideoChannelTest, TestChangeStreamParamsInContent) {
+  Base::TestChangeStreamParamsInContent();
+}
+
+TEST_F(VideoChannelTest, TestPlayoutAndSendingStates) {
+  Base::TestPlayoutAndSendingStates();
+}
+
+TEST_F(VideoChannelTest, TestCallSetup) {
+  Base::TestCallSetup();
+}
+
+TEST_F(VideoChannelTest, TestCallTeardownRtcpMux) {
+  Base::TestCallTeardownRtcpMux();
+}
+
+TEST_F(VideoChannelTest, SendRtpToRtp) {
+  Base::SendRtpToRtp();
+}
+
+TEST_F(VideoChannelTest, SendNoRtcpToNoRtcp) {
+  Base::SendNoRtcpToNoRtcp();
+}
+
+TEST_F(VideoChannelTest, SendNoRtcpToRtcp) {
+  Base::SendNoRtcpToRtcp();
+}
+
+TEST_F(VideoChannelTest, SendRtcpToNoRtcp) {
+  Base::SendRtcpToNoRtcp();
+}
+
+TEST_F(VideoChannelTest, SendRtcpToRtcp) {
+  Base::SendRtcpToRtcp();
+}
+
+TEST_F(VideoChannelTest, SendRtcpMuxToRtcp) {
+  Base::SendRtcpMuxToRtcp();
+}
+
+TEST_F(VideoChannelTest, SendRtcpMuxToRtcpMux) {
+  Base::SendRtcpMuxToRtcpMux();
+}
+
+TEST_F(VideoChannelTest, SendEarlyRtcpMuxToRtcp) {
+  Base::SendEarlyRtcpMuxToRtcp();
+}
+
+TEST_F(VideoChannelTest, SendEarlyRtcpMuxToRtcpMux) {
+  Base::SendEarlyRtcpMuxToRtcpMux();
+}
+
+TEST_F(VideoChannelTest, SendSrtpToSrtp) {
+  Base::SendSrtpToSrtp();
+}
+
+TEST_F(VideoChannelTest, SendSrtpToRtp) {
+  Base::SendSrtpToSrtp();
+}
+
+TEST_F(VideoChannelTest, SendSrtcpMux) {
+  Base::SendSrtcpMux();
+}
+
+TEST_F(VideoChannelTest, SendRtpToRtpOnThread) {
+  Base::SendRtpToRtpOnThread();
+}
+
+TEST_F(VideoChannelTest, SendSrtpToSrtpOnThread) {
+  Base::SendSrtpToSrtpOnThread();
+}
+
+TEST_F(VideoChannelTest, SendWithWritabilityLoss) {
+  Base::SendWithWritabilityLoss();
+}
+
+TEST_F(VideoChannelTest, TestMediaMonitor) {
+  Base::TestMediaMonitor();
+}
+
+TEST_F(VideoChannelTest, TestMediaSinks) {
+  Base::TestMediaSinks();
+}
+
+TEST_F(VideoChannelTest, TestSetContentFailure) {
+  Base::TestSetContentFailure();
+}
+
+TEST_F(VideoChannelTest, TestFlushRtcp) {
+  Base::TestFlushRtcp();
+}
+
+TEST_F(VideoChannelTest, SendSsrcMuxToSsrcMux) {
+  Base::SendSsrcMuxToSsrcMux();
+}
+
+TEST_F(VideoChannelTest, SendSsrcMuxToSsrcMuxWithRtcpMux) {
+  Base::SendSsrcMuxToSsrcMuxWithRtcpMux();
+}
+
+// TODO: Add VideoChannelTest.TestChangeStateError.
+
+TEST_F(VideoChannelTest, TestSrtpError) {
+  Base::TestSrtpError();
+}
+
+TEST_F(VideoChannelTest, TestApplyViewRequest) {
+  CreateChannels(0, 0);
+  EXPECT_TRUE(SendInitiate());
+  EXPECT_TRUE(SendAccept());
+
+  cricket::VideoFormat send_format;
+  EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+  EXPECT_EQ(640, send_format.width);
+  EXPECT_EQ(400, send_format.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), send_format.interval);
+
+  cricket::ViewRequest request;
+  request.static_video_views.push_back(
+      cricket::StaticVideoView(kSsrc1, 320, 200, 15));
+  EXPECT_TRUE(channel1_->ApplyViewRequest(request));
+  EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+  EXPECT_EQ(320, send_format.width);
+  EXPECT_EQ(200, send_format.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(15), send_format.interval);
+
+  // Short term hack for view request with SSRC 0. TODO: Remove this
+  // once Reflector uses the correct SSRC in view request (b/5977302).
+  request.static_video_views.clear();
+  request.static_video_views.push_back(
+      cricket::StaticVideoView(0, 160, 100, 8));
+  EXPECT_TRUE(channel1_->ApplyViewRequest(request));
+  EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+  EXPECT_EQ(160, send_format.width);
+  EXPECT_EQ(100, send_format.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(8), send_format.interval);
+
+  request.static_video_views.clear();
+  EXPECT_TRUE(channel1_->ApplyViewRequest(request));
+  EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+  EXPECT_EQ(0, send_format.width);
+  EXPECT_EQ(0, send_format.height);
+}
diff --git a/talk/session/phone/channelmanager.cc b/talk/session/phone/channelmanager.cc
index 8f8a0fc..7cca61a 100644
--- a/talk/session/phone/channelmanager.cc
+++ b/talk/session/phone/channelmanager.cc
@@ -59,8 +59,16 @@
   MSG_DESTROYSOUNDCLIP = 18,
   MSG_CAMERASTARTED = 19,
   MSG_SETVIDEOCAPTURE = 20,
+  MSG_TERMINATE = 21,
+  MSG_REGISTERVIDEOPROCESSOR = 22,
+  MSG_UNREGISTERVIDEOPROCESSOR = 23,
+  MSG_REGISTERVOICEPROCESSOR = 24,
+  MSG_UNREGISTERVOICEPROCESSOR = 25,
+  MSG_SETVIDEOCAPTURER = 26,
 };
 
+static const int kNotSetOutputVolume = -1;
+
 struct CreationParams : public talk_base::MessageData {
   CreationParams(BaseSession* session, const std::string& content_name,
                  bool rtcp, VoiceChannel* voice_channel)
@@ -117,6 +125,16 @@
   bool result;
 };
 
+struct Capturer : public talk_base::MessageData {
+  Capturer(VideoCapturer* c, uint32 s)
+      : capturer(c),
+        ssrc(s),
+        result(false) {}
+  VideoCapturer* capturer;
+  uint32 ssrc;
+  bool result;
+};
+
 struct LoggingOptions : public talk_base::MessageData {
   explicit LoggingOptions(int lev, const char* f) : level(lev), filter(f) {}
   int level;
@@ -125,11 +143,27 @@
 
 struct CaptureParams : public talk_base::MessageData {
   explicit CaptureParams(bool c) : capture(c), result(CR_FAILURE) {}
-
   bool capture;
   CaptureResult result;
 };
 
+struct VideoProcessorParams : public talk_base::MessageData {
+  VideoProcessorParams(uint32 s, VideoProcessor* p)
+      : ssrc(s), processor(p), result(false) {}
+  uint32 ssrc;
+  VideoProcessor* processor;
+  bool result;
+};
+
+struct VoiceProcessorParams : public talk_base::MessageData {
+  VoiceProcessorParams(uint32 c, VoiceProcessor* p, MediaProcessorDirection d)
+      : ssrc(c), direction(d), processor(p), result(false) {}
+  uint32 ssrc;
+  MediaProcessorDirection direction;
+  VoiceProcessor* processor;
+  bool result;
+};
+
 ChannelManager::ChannelManager(talk_base::Thread* worker_thread)
     : media_engine_(MediaEngineFactory::Create()),
       device_manager_(cricket::DeviceManagerFactory::Create()),
@@ -139,6 +173,7 @@
       audio_in_device_(DeviceManagerInterface::kDefaultDeviceName),
       audio_out_device_(DeviceManagerInterface::kDefaultDeviceName),
       audio_options_(MediaEngineInterface::DEFAULT_AUDIO_OPTIONS),
+      audio_output_volume_(kNotSetOutputVolume),
       local_renderer_(NULL),
       capturing_(false),
       monitoring_(false) {
@@ -156,6 +191,7 @@
       audio_in_device_(DeviceManagerInterface::kDefaultDeviceName),
       audio_out_device_(DeviceManagerInterface::kDefaultDeviceName),
       audio_options_(MediaEngineInterface::DEFAULT_AUDIO_OPTIONS),
+      audio_output_volume_(kNotSetOutputVolume),
       local_renderer_(NULL),
       capturing_(false),
       monitoring_(false) {
@@ -166,8 +202,6 @@
   // Init the device manager immediately, and set up our default video device.
   SignalDevicesChange.repeat(device_manager_->SignalDevicesChange);
   device_manager_->Init();
-  // Set camera_device_ to the name of the default video capturer.
-  SetVideoOptions(DeviceManagerInterface::kDefaultDeviceName);
 
   // Camera is started asynchronously, request callbacks when startup
   // completes to be able to forward them to the rendering manager.
@@ -250,6 +284,14 @@
                         << " speaker: " << audio_out_device_
                         << " options: " << audio_options_;
       }
+
+      // If audio_output_volume_ has been set via SetOutputVolume(), set the
+      // audio output volume of the engine.
+      if (kNotSetOutputVolume != audio_output_volume_ &&
+          !SetOutputVolume(audio_output_volume_)) {
+        LOG(LS_WARNING) << "Failed to SetOutputVolume to "
+                        << audio_output_volume_;
+      }
       if (!SetVideoOptions(camera_device_) && !camera_device_.empty()) {
         LOG(LS_WARNING) << "Failed to SetVideoOptions with camera: "
                         << camera_device_;
@@ -278,8 +320,14 @@
   if (!initialized_) {
     return;
   }
+  Send(MSG_TERMINATE, NULL);
+  media_engine_->Terminate();
+  initialized_ = false;
+}
 
-  // Need to destroy the voice/video channels
+void ChannelManager::Terminate_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+    // Need to destroy the voice/video channels
   while (!video_channels_.empty()) {
     DestroyVideoChannel_w(video_channels_.back());
   }
@@ -289,9 +337,6 @@
   while (!soundclips_.empty()) {
     DestroySoundclip_w(soundclips_.back());
   }
-
-  media_engine_->Terminate();
-  initialized_ = false;
 }
 
 VoiceChannel* ChannelManager::CreateVoiceChannel(
@@ -302,8 +347,6 @@
 
 VoiceChannel* ChannelManager::CreateVoiceChannel_w(
     BaseSession* session, const std::string& content_name, bool rtcp) {
-  talk_base::CritScope cs(&crit_);
-
   // This is ok to alloc from a thread other than the worker thread
   ASSERT(initialized_);
   VoiceMediaChannel* media_channel = media_engine_->CreateChannel();
@@ -329,7 +372,6 @@
 }
 
 void ChannelManager::DestroyVoiceChannel_w(VoiceChannel* voice_channel) {
-  talk_base::CritScope cs(&crit_);
   // Destroy voice channel.
   ASSERT(initialized_);
   VoiceChannels::iterator it = std::find(voice_channels_.begin(),
@@ -352,8 +394,6 @@
 VideoChannel* ChannelManager::CreateVideoChannel_w(
     BaseSession* session, const std::string& content_name, bool rtcp,
     VoiceChannel* voice_channel) {
-  talk_base::CritScope cs(&crit_);
-
   // This is ok to alloc from a thread other than the worker thread
   ASSERT(initialized_);
   VideoMediaChannel* media_channel =
@@ -382,7 +422,6 @@
 }
 
 void ChannelManager::DestroyVideoChannel_w(VideoChannel *video_channel) {
-  talk_base::CritScope cs(&crit_);
   // Destroy voice channel.
   ASSERT(initialized_);
   VideoChannels::iterator it = std::find(video_channels_.begin(),
@@ -402,8 +441,6 @@
 }
 
 Soundclip* ChannelManager::CreateSoundclip_w() {
-  talk_base::CritScope cs(&crit_);
-
   ASSERT(initialized_);
   ASSERT(worker_thread_ == talk_base::Thread::Current());
 
@@ -425,7 +462,6 @@
 }
 
 void ChannelManager::DestroySoundclip_w(Soundclip* soundclip) {
-  talk_base::CritScope cs(&crit_);
   // Destroy soundclip.
   ASSERT(initialized_);
   Soundclips::iterator it = std::find(soundclips_.begin(),
@@ -485,7 +521,6 @@
 
   // Set the audio devices
   if (ret) {
-    talk_base::CritScope cs(&crit_);
     ret = media_engine_->SetSoundDevices(in_dev, out_dev);
   }
 
@@ -509,8 +544,17 @@
 }
 
 bool ChannelManager::SetOutputVolume(int level) {
-  VolumeLevel volume(level);
-  return (Send(MSG_SETOUTPUTVOLUME, &volume) && volume.result);
+  bool ret = level >= 0 && level <= 255;
+  if (initialized_) {
+    VolumeLevel volume(level);
+    ret &= Send(MSG_SETOUTPUTVOLUME, &volume) && volume.result;
+  }
+
+  if (ret) {
+    audio_output_volume_ = level;
+  }
+
+  return ret;
 }
 
 bool ChannelManager::SetOutputVolume_w(int level) {
@@ -520,22 +564,33 @@
 }
 
 bool ChannelManager::GetVideoOptions(std::string* cam_name) {
+  if (camera_device_.empty()) {
+    // Initialize camera_device_ with default.
+    Device device;
+    if (!device_manager_->GetVideoCaptureDevice(
+        DeviceManagerInterface::kDefaultDeviceName, &device)) {
+      LOG(LS_WARNING) << "Device manager can't find default camera: " <<
+          DeviceManagerInterface::kDefaultDeviceName;
+      return false;
+    }
+    camera_device_ = device.name;
+  }
   *cam_name = camera_device_;
   return true;
 }
 
 bool ChannelManager::SetVideoOptions(const std::string& cam_name) {
   Device device;
+  bool ret = true;
   if (!device_manager_->GetVideoCaptureDevice(cam_name, &device)) {
     if (!cam_name.empty()) {
       LOG(LS_WARNING) << "Device manager can't find camera: " << cam_name;
     }
-    return false;
+    ret = false;
   }
 
   // If we're running, tell the media engine about it.
-  bool ret = true;
-  if (initialized_) {
+  if (initialized_ && ret) {
     VideoOptions options(&device);
     ret = (Send(MSG_SETVIDEOOPTIONS, &options) && options.result);
   }
@@ -543,7 +598,18 @@
   // If everything worked, retain the name of the selected camera.
   if (ret) {
     camera_device_ = device.name;
+  } else if (camera_device_.empty()) {
+    // When video option setting fails, we still want camera_device_ to be in a
+    // good state, so we initialize it with default if it's empty.
+    Device default_device;
+    if (!device_manager_->GetVideoCaptureDevice(
+        DeviceManagerInterface::kDefaultDeviceName, &default_device)) {
+      LOG(LS_WARNING) << "Device manager can't find default camera: " <<
+          DeviceManagerInterface::kDefaultDeviceName;
+    }
+    camera_device_ = default_device.name;
   }
+
   return ret;
 }
 
@@ -607,6 +673,21 @@
   return media_engine_->SetLocalRenderer(renderer);
 }
 
+bool ChannelManager::SetVideoCapturer(VideoCapturer* capturer, uint32 ssrc) {
+  bool ret = true;
+  if (initialized_) {
+    Capturer capture(capturer, ssrc);
+    ret = (Send(MSG_SETVIDEOCAPTURER, &capture) && capture.result);
+  }
+  return ret;
+}
+
+bool ChannelManager::SetVideoCapturer_w(VideoCapturer* capturer, uint32 ssrc) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(initialized_);
+  return media_engine_->SetVideoCapturer(capturer, ssrc);
+}
+
 CaptureResult ChannelManager::SetVideoCapture(bool capture) {
   bool ret;
   CaptureParams capture_params(capture);
@@ -655,6 +736,62 @@
   }
 }
 
+// TODO: For now pass this request through the mediaengine to the
+// voice and video engines to do the real work. Once the capturer refactoring
+// is done, we will access the capturer using the ssrc (similar to how the
+// renderer is accessed today) and register with it directly.
+bool ChannelManager::RegisterVideoProcessor(uint32 ssrc,
+                                            VideoProcessor* processor) {
+  VideoProcessorParams processor_params(ssrc, processor);
+  return (Send(MSG_REGISTERVIDEOPROCESSOR, &processor_params) &&
+      processor_params.result);
+}
+bool ChannelManager::RegisterVideoProcessor_w(uint32 ssrc,
+                                              VideoProcessor* processor) {
+  return media_engine_->RegisterVideoProcessor(processor);
+}
+
+bool ChannelManager::UnregisterVideoProcessor(uint32 ssrc,
+                                              VideoProcessor* processor) {
+  VideoProcessorParams processor_params(ssrc, processor);
+  return (Send(MSG_UNREGISTERVIDEOPROCESSOR, &processor_params) &&
+      processor_params.result);
+}
+bool ChannelManager::UnregisterVideoProcessor_w(uint32 ssrc,
+                                                VideoProcessor* processor) {
+  return media_engine_->UnregisterVideoProcessor(processor);
+}
+
+bool ChannelManager::RegisterVoiceProcessor(
+    uint32 ssrc,
+    VoiceProcessor* processor,
+    MediaProcessorDirection direction) {
+  VoiceProcessorParams processor_params(ssrc, processor, direction);
+  return (Send(MSG_REGISTERVOICEPROCESSOR, &processor_params) &&
+      processor_params.result);
+}
+bool ChannelManager::RegisterVoiceProcessor_w(
+    uint32 ssrc,
+    VoiceProcessor* processor,
+    MediaProcessorDirection direction) {
+  return media_engine_->RegisterVoiceProcessor(ssrc, processor, direction);
+}
+
+bool ChannelManager::UnregisterVoiceProcessor(
+    uint32 ssrc,
+    VoiceProcessor* processor,
+    MediaProcessorDirection direction) {
+  VoiceProcessorParams processor_params(ssrc, processor, direction);
+  return (Send(MSG_UNREGISTERVOICEPROCESSOR, &processor_params) &&
+      processor_params.result);
+}
+bool ChannelManager::UnregisterVoiceProcessor_w(
+    uint32 ssrc,
+    VoiceProcessor* processor,
+    MediaProcessorDirection direction) {
+  return media_engine_->UnregisterVoiceProcessor(ssrc, processor, direction);
+}
+
 bool ChannelManager::Send(uint32 id, talk_base::MessageData* data) {
   if (!worker_thread_ || !initialized_) return false;
   worker_thread_->Send(this, id, data);
@@ -746,6 +883,11 @@
       p->result = SetLocalRenderer_w(p->renderer);
       break;
     }
+    case MSG_SETVIDEOCAPTURER: {
+      Capturer* p = static_cast<Capturer*>(data);
+      p->result = SetVideoCapturer_w(p->capturer, p->ssrc);
+      break;
+    }
     case MSG_SETVIDEOCAPTURE: {
       CaptureParams* p = static_cast<CaptureParams*>(data);
       p->result = SetVideoCapture_w(p->capture);
@@ -766,6 +908,38 @@
       delete data;
       break;
     }
+    case MSG_TERMINATE: {
+      Terminate_w();
+      break;
+    }
+    case MSG_REGISTERVIDEOPROCESSOR: {
+      VideoProcessorParams* data =
+          static_cast<VideoProcessorParams*>(message->pdata);
+      data->result = RegisterVideoProcessor_w(data->ssrc, data->processor);
+      break;
+    }
+    case MSG_UNREGISTERVIDEOPROCESSOR: {
+      VideoProcessorParams* data =
+          static_cast<VideoProcessorParams*>(message->pdata);
+      data->result = UnregisterVideoProcessor_w(data->ssrc, data->processor);
+      break;
+    }
+    case MSG_REGISTERVOICEPROCESSOR: {
+      VoiceProcessorParams* data =
+          static_cast<VoiceProcessorParams*>(message->pdata);
+      data->result = RegisterVoiceProcessor_w(data->ssrc,
+                                              data->processor,
+                                              data->direction);
+      break;
+    }
+    case MSG_UNREGISTERVOICEPROCESSOR: {
+      VoiceProcessorParams* data =
+          static_cast<VoiceProcessorParams*>(message->pdata);
+      data->result = UnregisterVoiceProcessor_w(data->ssrc,
+                                              data->processor,
+                                              data->direction);
+      break;
+    }
   }
 }
 
diff --git a/talk/session/phone/channelmanager.h b/talk/session/phone/channelmanager.h
index c574a43..9d100ee 100644
--- a/talk/session/phone/channelmanager.h
+++ b/talk/session/phone/channelmanager.h
@@ -41,7 +41,9 @@
 namespace cricket {
 
 class Soundclip;
+class VideoProcessor;
 class VoiceChannel;
+class VoiceProcessor;
 
 // ChannelManager allows the MediaEngine to run on a separate thread, and takes
 // care of marshalling calls between threads. It also creates and keeps track of
@@ -83,10 +85,6 @@
   bool initialized() const { return initialized_; }
   // Starts up the media engine.
   bool Init();
-  // TODO: Remove this temporary API once Flute is updated.
-  bool Init(talk_base::Thread* thread) {
-    return set_worker_thread(thread) && Init();
-  }
   // Shuts down the media engine.
   void Terminate();
 
@@ -132,6 +130,9 @@
   bool monitoring() const { return monitoring_; }
   // Sets the local renderer where to renderer the local camera.
   bool SetLocalRenderer(VideoRenderer* renderer);
+  // Sets the externally provided video capturer. The ssrc is the ssrc of the
+  // (video) stream for which the video capturer should be set.
+  bool SetVideoCapturer(VideoCapturer* capturer, uint32 ssrc);
   // Starts and stops the local camera and renders it to the local renderer.
   CaptureResult SetVideoCapture(bool capture);
   bool capturing() const { return capturing_; }
@@ -140,6 +141,21 @@
   void SetVoiceLogging(int level, const char* filter);
   void SetVideoLogging(int level, const char* filter);
 
+  // The channel manager handles the Tx side for Video processing,
+  // as well as Tx and Rx side for Voice processing.
+  // (The Rx Video processing will go throug the simplerenderingmanager,
+  //  to be implemented).
+  bool RegisterVideoProcessor(uint32 ssrc,
+                              VideoProcessor* processor);
+  bool UnregisterVideoProcessor(uint32 ssrc,
+                                VideoProcessor* processor);
+  bool RegisterVoiceProcessor(uint32 ssrc,
+                              VoiceProcessor* processor,
+                              MediaProcessorDirection direction);
+  bool UnregisterVoiceProcessor(uint32 ssrc,
+                                VoiceProcessor* processor,
+                                MediaProcessorDirection direction);
+
   // The operations below occur on the main thread.
 
   bool GetAudioInputDevices(std::vector<std::string>* names);
@@ -155,6 +171,7 @@
 
   void Construct();
   bool Send(uint32 id, talk_base::MessageData* pdata);
+  void Terminate_w();
   VoiceChannel* CreateVoiceChannel_w(
       BaseSession* session, const std::string& content_name, bool rtcp);
   void DestroyVoiceChannel_w(VoiceChannel* voice_channel);
@@ -172,14 +189,25 @@
   bool SetVideoOptions_w(const Device* cam_device);
   bool SetDefaultVideoEncoderConfig_w(const VideoEncoderConfig& config);
   bool SetLocalRenderer_w(VideoRenderer* renderer);
+  bool SetVideoCapturer_w(VideoCapturer* capturer, uint32 ssrc);
   CaptureResult SetVideoCapture_w(bool capture);
   void SetMediaLogging(bool video, int level, const char* filter);
   void SetMediaLogging_w(bool video, int level, const char* filter);
   void OnVideoCaptureResult(VideoCapturer* capturer,
                             CaptureResult result);
+  bool RegisterVideoProcessor_w(uint32 ssrc,
+                                VideoProcessor* processor);
+  bool UnregisterVideoProcessor_w(uint32 ssrc,
+                                  VideoProcessor* processor);
+  bool RegisterVoiceProcessor_w(uint32 ssrc,
+                                VoiceProcessor* processor,
+                                MediaProcessorDirection direction);
+  bool UnregisterVoiceProcessor_w(uint32 ssrc,
+                                  VoiceProcessor* processor,
+                                  MediaProcessorDirection direction);
+
   void OnMessage(talk_base::Message *message);
 
-  talk_base::CriticalSection crit_;
   talk_base::scoped_ptr<MediaEngineInterface> media_engine_;
   talk_base::scoped_ptr<DeviceManagerInterface> device_manager_;
   bool initialized_;
@@ -193,6 +221,7 @@
   std::string audio_in_device_;
   std::string audio_out_device_;
   int audio_options_;
+  int audio_output_volume_;
   std::string camera_device_;
   VideoEncoderConfig default_video_encoder_config_;
   VideoRenderer* local_renderer_;
diff --git a/talk/session/phone/channelmanager_unittest.cc b/talk/session/phone/channelmanager_unittest.cc
new file mode 100644
index 0000000..0c132ac
--- /dev/null
+++ b/talk/session/phone/channelmanager_unittest.cc
@@ -0,0 +1,523 @@
+// libjingle
+// Copyright 2008 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/fakedevicemanager.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/fakemediaprocessor.h"
+#include "talk/session/phone/nullvideorenderer.h"
+
+class ChannelManagerTest : public testing::Test {
+ protected:
+  ChannelManagerTest() : fme_(NULL), fdm_(NULL), cm_(NULL) {
+  }
+
+  virtual void SetUp() {
+    fme_ = new cricket::FakeMediaEngine();
+    fdm_ = new cricket::FakeDeviceManager();
+    cm_ = new cricket::ChannelManager(fme_, fdm_, talk_base::Thread::Current());
+    session_ = new cricket::FakeSession();
+
+    std::vector<std::string> in_device_list, out_device_list, vid_device_list;
+    in_device_list.push_back("audio-in1");
+    in_device_list.push_back("audio-in2");
+    out_device_list.push_back("audio-out1");
+    out_device_list.push_back("audio-out2");
+    vid_device_list.push_back("video-in1");
+    vid_device_list.push_back("video-in2");
+    fdm_->SetAudioInputDevices(in_device_list);
+    fdm_->SetAudioOutputDevices(out_device_list);
+    fdm_->SetVideoCaptureDevices(vid_device_list);
+  }
+
+  virtual void TearDown() {
+    delete session_;
+    delete cm_;
+    cm_ = NULL;
+    fdm_ = NULL;
+    fme_ = NULL;
+  }
+
+  talk_base::Thread worker_;
+  cricket::FakeMediaEngine* fme_;
+  cricket::FakeDeviceManager* fdm_;
+  cricket::ChannelManager* cm_;
+  cricket::FakeSession* session_;
+};
+
+// Test that we startup/shutdown properly.
+TEST_F(ChannelManagerTest, StartupShutdown) {
+  EXPECT_FALSE(cm_->initialized());
+  EXPECT_EQ(talk_base::Thread::Current(), cm_->worker_thread());
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->initialized());
+  cm_->Terminate();
+  EXPECT_FALSE(cm_->initialized());
+}
+
+// Test that we startup/shutdown properly with a worker thread.
+TEST_F(ChannelManagerTest, StartupShutdownOnThread) {
+  worker_.Start();
+  EXPECT_FALSE(cm_->initialized());
+  EXPECT_EQ(talk_base::Thread::Current(), cm_->worker_thread());
+  EXPECT_TRUE(cm_->set_worker_thread(&worker_));
+  EXPECT_EQ(&worker_, cm_->worker_thread());
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->initialized());
+  // Setting the worker thread while initialized should fail.
+  EXPECT_FALSE(cm_->set_worker_thread(talk_base::Thread::Current()));
+  cm_->Terminate();
+  EXPECT_FALSE(cm_->initialized());
+}
+
+// Test that we fail to startup if we're given an unstarted thread.
+TEST_F(ChannelManagerTest, StartupShutdownOnUnstartedThread) {
+  EXPECT_TRUE(cm_->set_worker_thread(&worker_));
+  EXPECT_FALSE(cm_->Init());
+  EXPECT_FALSE(cm_->initialized());
+}
+
+// Test that we can create and destroy a voice and video channel.
+TEST_F(ChannelManagerTest, CreateDestroyChannels) {
+  EXPECT_TRUE(cm_->Init());
+  cricket::VoiceChannel* voice_channel = cm_->CreateVoiceChannel(
+      session_, cricket::CN_AUDIO, false);
+  EXPECT_TRUE(voice_channel != NULL);
+  cricket::VideoChannel* video_channel =
+      cm_->CreateVideoChannel(session_, cricket::CN_VIDEO,
+                              false, voice_channel);
+  EXPECT_TRUE(video_channel != NULL);
+  cm_->DestroyVideoChannel(video_channel);
+  cm_->DestroyVoiceChannel(voice_channel);
+  cm_->Terminate();
+}
+
+// Test that we can create and destroy a voice and video channel with a worker.
+TEST_F(ChannelManagerTest, CreateDestroyChannelsOnThread) {
+  worker_.Start();
+  EXPECT_TRUE(cm_->set_worker_thread(&worker_));
+  EXPECT_TRUE(cm_->Init());
+  cricket::VoiceChannel* voice_channel = cm_->CreateVoiceChannel(
+      session_, cricket::CN_AUDIO, false);
+  EXPECT_TRUE(voice_channel != NULL);
+  cricket::VideoChannel* video_channel =
+      cm_->CreateVideoChannel(session_, cricket::CN_VIDEO,
+                              false, voice_channel);
+  EXPECT_TRUE(video_channel != NULL);
+  cm_->DestroyVideoChannel(video_channel);
+  cm_->DestroyVoiceChannel(voice_channel);
+  cm_->Terminate();
+}
+
+// Test that we fail to create a voice/video channel if the session is unable
+// to create a cricket::TransportChannel
+TEST_F(ChannelManagerTest, NoTransportChannelTest) {
+  EXPECT_TRUE(cm_->Init());
+  session_->set_fail_channel_creation(true);
+  // The test is useless unless the session does not fail creating
+  // cricket::TransportChannel.
+  ASSERT_TRUE(session_->CreateChannel("audio", "rtp") == NULL);
+
+  cricket::VoiceChannel* voice_channel = cm_->CreateVoiceChannel(
+      session_, cricket::CN_AUDIO, false);
+  EXPECT_TRUE(voice_channel == NULL);
+  cricket::VideoChannel* video_channel =
+      cm_->CreateVideoChannel(session_, cricket::CN_VIDEO,
+                              false, voice_channel);
+  EXPECT_TRUE(video_channel == NULL);
+  cm_->Terminate();
+}
+
+// Test that SetDefaultVideoCodec passes through the right values.
+TEST_F(ChannelManagerTest, SetDefaultVideoEncoderConfig) {
+  cricket::VideoCodec codec(96, "G264", 1280, 720, 60, 0);
+  cricket::VideoEncoderConfig config(codec, 1, 2);
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->SetDefaultVideoEncoderConfig(config));
+  EXPECT_EQ(config, fme_->default_video_encoder_config());
+}
+
+// Test that SetDefaultVideoCodec passes through the right values.
+TEST_F(ChannelManagerTest, SetDefaultVideoCodecBeforeInit) {
+  cricket::VideoCodec codec(96, "G264", 1280, 720, 60, 0);
+  cricket::VideoEncoderConfig config(codec, 1, 2);
+  EXPECT_TRUE(cm_->SetDefaultVideoEncoderConfig(config));
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ(config, fme_->default_video_encoder_config());
+}
+
+TEST_F(ChannelManagerTest, SetAudioOptionsBeforeInit) {
+  // Test that values that we set before Init are applied.
+  EXPECT_TRUE(cm_->SetAudioOptions("audio-in1", "audio-out1", 0x2));
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ("audio-in1", fme_->audio_in_device());
+  EXPECT_EQ("audio-out1", fme_->audio_out_device());
+  EXPECT_EQ(0x2, fme_->audio_options());
+}
+
+TEST_F(ChannelManagerTest, GetAudioOptionsBeforeInit) {
+  std::string audio_in, audio_out;
+  int opts;
+  // Test that GetAudioOptions works before Init.
+  EXPECT_TRUE(cm_->SetAudioOptions("audio-in2", "audio-out2", 0x1));
+  EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+  EXPECT_EQ("audio-in2", audio_in);
+  EXPECT_EQ("audio-out2", audio_out);
+  EXPECT_EQ(0x1, opts);
+  // Test that options set before Init can be gotten after Init.
+  EXPECT_TRUE(cm_->SetAudioOptions("audio-in1", "audio-out1", 0x2));
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+  EXPECT_EQ("audio-in1", audio_in);
+  EXPECT_EQ("audio-out1", audio_out);
+  EXPECT_EQ(0x2, opts);
+}
+
+TEST_F(ChannelManagerTest, SetAudioOptions) {
+  // Test initial state.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ("", fme_->audio_in_device());
+  EXPECT_EQ("", fme_->audio_out_device());
+  EXPECT_EQ(cricket::MediaEngineInterface::DEFAULT_AUDIO_OPTIONS,
+            fme_->audio_options());
+  // Test setting defaults.
+  EXPECT_TRUE(cm_->SetAudioOptions("", "", 0x3));
+  EXPECT_EQ("", fme_->audio_in_device());
+  EXPECT_EQ("", fme_->audio_out_device());
+  EXPECT_EQ(0x3, fme_->audio_options());
+  // Test setting specific values.
+  EXPECT_TRUE(cm_->SetAudioOptions("audio-in1", "audio-out1", 0x2));
+  EXPECT_EQ("audio-in1", fme_->audio_in_device());
+  EXPECT_EQ("audio-out1", fme_->audio_out_device());
+  EXPECT_EQ(0x2, fme_->audio_options());
+  // Test setting bad values.
+  EXPECT_FALSE(cm_->SetAudioOptions("audio-in9", "audio-out2", 0x1));
+}
+
+TEST_F(ChannelManagerTest, GetAudioOptions) {
+  std::string audio_in, audio_out;
+  int opts;
+  // Test initial state.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+  EXPECT_EQ(std::string(cricket::DeviceManagerInterface::kDefaultDeviceName),
+            audio_in);
+  EXPECT_EQ(std::string(cricket::DeviceManagerInterface::kDefaultDeviceName),
+            audio_out);
+  EXPECT_EQ(cricket::MediaEngineInterface::DEFAULT_AUDIO_OPTIONS, opts);
+  // Test that we get back specific values that we set.
+  EXPECT_TRUE(cm_->SetAudioOptions("audio-in1", "audio-out1", 0x2));
+  EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+  EXPECT_EQ("audio-in1", audio_in);
+  EXPECT_EQ("audio-out1", audio_out);
+  EXPECT_EQ(0x2, opts);
+}
+
+TEST_F(ChannelManagerTest, SetVideoOptionsBeforeInit) {
+  // Test that values that we set before Init are applied.
+  EXPECT_TRUE(cm_->SetVideoOptions("video-in2"));
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ("video-in2", fme_->video_in_device());
+}
+
+TEST_F(ChannelManagerTest, GetVideoOptionsBeforeInit) {
+  std::string video_in;
+  // Test that GetVideoOptions works before Init.
+  EXPECT_TRUE(cm_->SetVideoOptions("video-in1"));
+  EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+  EXPECT_EQ("video-in1", video_in);
+  // Test that options set before Init can be gotten after Init.
+  EXPECT_TRUE(cm_->SetVideoOptions("video-in2"));
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+  EXPECT_EQ("video-in2", video_in);
+}
+
+TEST_F(ChannelManagerTest, SetVideoOptions) {
+  // Test setting defaults.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->SetVideoOptions(""));  // will use DeviceManager default
+  EXPECT_EQ("video-in1", fme_->video_in_device());
+  // Test setting specific values.
+  EXPECT_TRUE(cm_->SetVideoOptions("video-in2"));
+  EXPECT_EQ("video-in2", fme_->video_in_device());
+  // TODO: Add test for invalid value here.
+}
+
+// Test unplugging and plugging back the preferred devices. When the preferred
+// device is unplugged, we fall back to the default device. When the preferred
+// device is plugged back, we use it.
+TEST_F(ChannelManagerTest, SetAudioOptionsUnplugPlug) {
+  // Set preferences "audio-in1" and "audio-out1" before init.
+  EXPECT_TRUE(cm_->SetAudioOptions("audio-in1", "audio-out1", 0x2));
+  // Unplug device "audio-in1" and "audio-out1".
+  std::vector<std::string> in_device_list, out_device_list;
+  in_device_list.push_back("audio-in2");
+  out_device_list.push_back("audio-out2");
+  fdm_->SetAudioInputDevices(in_device_list);
+  fdm_->SetAudioOutputDevices(out_device_list);
+  // Init should fall back to default devices.
+  EXPECT_TRUE(cm_->Init());
+  // The media engine should use the default.
+  EXPECT_EQ("", fme_->audio_in_device());
+  EXPECT_EQ("", fme_->audio_out_device());
+  // The channel manager keeps the preferences "audio-in1" and "audio-out1".
+  std::string audio_in, audio_out;
+  int opts;
+  EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+  EXPECT_EQ("audio-in1", audio_in);
+  EXPECT_EQ("audio-out1", audio_out);
+  cm_->Terminate();
+
+  // Plug devices "audio-in2" and "audio-out2" back.
+  in_device_list.push_back("audio-in1");
+  out_device_list.push_back("audio-out1");
+  fdm_->SetAudioInputDevices(in_device_list);
+  fdm_->SetAudioOutputDevices(out_device_list);
+  // Init again. The preferences, "audio-in2" and "audio-out2", are used.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ("audio-in1", fme_->audio_in_device());
+  EXPECT_EQ("audio-out1", fme_->audio_out_device());
+  EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+  EXPECT_EQ("audio-in1", audio_in);
+  EXPECT_EQ("audio-out1", audio_out);
+}
+
+// We have one camera. Unplug it, fall back to no camera.
+TEST_F(ChannelManagerTest, SetVideoOptionsUnplugPlugOneCamera) {
+  // Set preferences "video-in1" before init.
+  std::vector<std::string> vid_device_list;
+  vid_device_list.push_back("video-in1");
+  fdm_->SetVideoCaptureDevices(vid_device_list);
+  EXPECT_TRUE(cm_->SetVideoOptions("video-in1"));
+
+  // Unplug "video-in1".
+  vid_device_list.clear();
+  fdm_->SetVideoCaptureDevices(vid_device_list);
+
+  // Init should fall back to avatar.
+  EXPECT_TRUE(cm_->Init());
+  // The media engine should use no camera.
+  EXPECT_EQ("", fme_->video_in_device());
+  // The channel manager keeps the user preference "video-in".
+  std::string video_in;
+  EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+  EXPECT_EQ("video-in1", video_in);
+  cm_->Terminate();
+
+  // Plug device "video-in1" back.
+  vid_device_list.push_back("video-in1");
+  fdm_->SetVideoCaptureDevices(vid_device_list);
+  // Init again. The user preferred device, "video-in1", is used.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ("video-in1", fme_->video_in_device());
+  EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+  EXPECT_EQ("video-in1", video_in);
+}
+
+// We have multiple cameras. Unplug the preferred, fall back to another camera.
+TEST_F(ChannelManagerTest, SetVideoOptionsUnplugPlugTwoDevices) {
+  // Set video device to "video-in1" before init.
+  EXPECT_TRUE(cm_->SetVideoOptions("video-in1"));
+  // Unplug device "video-in1".
+  std::vector<std::string> vid_device_list;
+  vid_device_list.push_back("video-in2");
+  fdm_->SetVideoCaptureDevices(vid_device_list);
+  // Init should fall back to default device "video-in2".
+  EXPECT_TRUE(cm_->Init());
+  // The media engine should use the default device "video-in2".
+  EXPECT_EQ("video-in2", fme_->video_in_device());
+  // The channel manager keeps the user preference "video-in".
+  std::string video_in;
+  EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+  EXPECT_EQ("video-in1", video_in);
+  cm_->Terminate();
+
+  // Plug device "video-in1" back.
+  vid_device_list.push_back("video-in1");
+  fdm_->SetVideoCaptureDevices(vid_device_list);
+  // Init again. The user preferred device, "video-in1", is used.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ("video-in1", fme_->video_in_device());
+  EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+  EXPECT_EQ("video-in1", video_in);
+}
+
+TEST_F(ChannelManagerTest, GetVideoOptions) {
+  std::string video_in;
+  // Test setting/getting defaults.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->SetVideoOptions(""));
+  EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+  EXPECT_EQ("video-in1", video_in);
+  // Test setting/getting specific values.
+  EXPECT_TRUE(cm_->SetVideoOptions("video-in2"));
+  EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+  EXPECT_EQ("video-in2", video_in);
+}
+
+TEST_F(ChannelManagerTest, GetSetOutputVolumeBeforeInit) {
+  int level;
+  // Before init, SetOutputVolume() remembers the volume but does not change the
+  // volume of the engine. GetOutputVolume() should fail.
+  EXPECT_EQ(-1, fme_->output_volume());
+  EXPECT_FALSE(cm_->GetOutputVolume(&level));
+  EXPECT_FALSE(cm_->SetOutputVolume(-1));  // Invalid volume.
+  EXPECT_TRUE(cm_->SetOutputVolume(99));
+  EXPECT_EQ(-1, fme_->output_volume());
+
+  // Init() will apply the remembered volume.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->GetOutputVolume(&level));
+  EXPECT_EQ(99, level);
+  EXPECT_EQ(level, fme_->output_volume());
+
+  EXPECT_TRUE(cm_->SetOutputVolume(60));
+  EXPECT_TRUE(cm_->GetOutputVolume(&level));
+  EXPECT_EQ(60, level);
+  EXPECT_EQ(level, fme_->output_volume());
+}
+
+TEST_F(ChannelManagerTest, GetSetOutputVolume) {
+  int level;
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->GetOutputVolume(&level));
+  EXPECT_EQ(level, fme_->output_volume());
+
+  EXPECT_FALSE(cm_->SetOutputVolume(-1));  // Invalid volume.
+  EXPECT_TRUE(cm_->SetOutputVolume(60));
+  EXPECT_EQ(60, fme_->output_volume());
+  EXPECT_TRUE(cm_->GetOutputVolume(&level));
+  EXPECT_EQ(60, level);
+}
+
+// Test that a value set before Init is applied properly.
+TEST_F(ChannelManagerTest, SetLocalRendererBeforeInit) {
+  cricket::NullVideoRenderer renderer;
+  EXPECT_TRUE(cm_->SetLocalRenderer(&renderer));
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ(&renderer, fme_->local_renderer());
+}
+
+// Test that a value set after init is passed through properly.
+TEST_F(ChannelManagerTest, SetLocalRenderer) {
+  cricket::NullVideoRenderer renderer;
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->SetLocalRenderer(&renderer));
+  EXPECT_EQ(&renderer, fme_->local_renderer());
+}
+
+// Test that logging options set before Init are applied properly,
+// and retained even after Init.
+TEST_F(ChannelManagerTest, SetLoggingBeforeInit) {
+  cm_->SetVoiceLogging(talk_base::LS_INFO, "test-voice");
+  cm_->SetVideoLogging(talk_base::LS_VERBOSE, "test-video");
+  EXPECT_EQ(talk_base::LS_INFO, fme_->voice_loglevel());
+  EXPECT_STREQ("test-voice", fme_->voice_logfilter().c_str());
+  EXPECT_EQ(talk_base::LS_VERBOSE, fme_->video_loglevel());
+  EXPECT_STREQ("test-video", fme_->video_logfilter().c_str());
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ(talk_base::LS_INFO, fme_->voice_loglevel());
+  EXPECT_STREQ("test-voice", fme_->voice_logfilter().c_str());
+  EXPECT_EQ(talk_base::LS_VERBOSE, fme_->video_loglevel());
+  EXPECT_STREQ("test-video", fme_->video_logfilter().c_str());
+}
+
+// Test that logging options set after Init are applied properly.
+TEST_F(ChannelManagerTest, SetLogging) {
+  EXPECT_TRUE(cm_->Init());
+  cm_->SetVoiceLogging(talk_base::LS_INFO, "test-voice");
+  cm_->SetVideoLogging(talk_base::LS_VERBOSE, "test-video");
+  EXPECT_EQ(talk_base::LS_INFO, fme_->voice_loglevel());
+  EXPECT_STREQ("test-voice", fme_->voice_logfilter().c_str());
+  EXPECT_EQ(talk_base::LS_VERBOSE, fme_->video_loglevel());
+  EXPECT_STREQ("test-video", fme_->video_logfilter().c_str());
+}
+
+// Test that SetVideoCapture passes through the right value.
+TEST_F(ChannelManagerTest, SetVideoCapture) {
+  // Should fail until we are initialized.
+  EXPECT_FALSE(fme_->capture());
+  EXPECT_EQ(cricket::CR_FAILURE, cm_->SetVideoCapture(true));
+  EXPECT_FALSE(fme_->capture());
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_FALSE(fme_->capture());
+  EXPECT_EQ(cricket::CR_SUCCESS, cm_->SetVideoCapture(true));
+  EXPECT_TRUE(fme_->capture());
+  EXPECT_EQ(cricket::CR_SUCCESS, cm_->SetVideoCapture(false));
+  EXPECT_FALSE(fme_->capture());
+}
+
+// Test that the Video/Voice Processors register and unregister
+TEST_F(ChannelManagerTest, RegisterProcessors) {
+  cricket::FakeMediaProcessor fmp;
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_FALSE(fme_->video_processor_registered());
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+  EXPECT_TRUE(cm_->RegisterVideoProcessor(1, &fmp));
+  EXPECT_TRUE(fme_->video_processor_registered());
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+  EXPECT_TRUE(cm_->UnregisterVideoProcessor(1, &fmp));
+  EXPECT_FALSE(fme_->video_processor_registered());
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+  EXPECT_TRUE(cm_->RegisterVoiceProcessor(1,
+                                          &fmp,
+                                          cricket::MPD_RX));
+  EXPECT_FALSE(fme_->video_processor_registered());
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+  EXPECT_TRUE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+
+  EXPECT_TRUE(cm_->UnregisterVoiceProcessor(1,
+                                            &fmp,
+                                            cricket::MPD_RX));
+  EXPECT_FALSE(fme_->video_processor_registered());
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+  EXPECT_TRUE(cm_->RegisterVoiceProcessor(1,
+                                          &fmp,
+                                          cricket::MPD_TX));
+  EXPECT_FALSE(fme_->video_processor_registered());
+  EXPECT_TRUE(fme_->voice_processor_registered(cricket::MPD_TX));
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+  EXPECT_TRUE(cm_->UnregisterVoiceProcessor(1,
+                                            &fmp,
+                                            cricket::MPD_TX));
+  EXPECT_FALSE(fme_->video_processor_registered());
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+}
diff --git a/talk/session/phone/codec_unittest.cc b/talk/session/phone/codec_unittest.cc
new file mode 100644
index 0000000..18e1eb6
--- /dev/null
+++ b/talk/session/phone/codec_unittest.cc
@@ -0,0 +1,222 @@
+/*
+ * libjingle
+ * Copyright 2009--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/codec.h"
+
+using cricket::AudioCodec;
+using cricket::VideoCodec;
+using cricket::VideoEncoderConfig;
+
+class CodecTest : public testing::Test {
+ public:
+  CodecTest() {}
+};
+
+TEST_F(CodecTest, TestAudioCodecOperators) {
+  AudioCodec c0(96, "A", 44100, 20000, 2, 3);
+  AudioCodec c1(95, "A", 44100, 20000, 2, 3);
+  AudioCodec c2(96, "x", 44100, 20000, 2, 3);
+  AudioCodec c3(96, "A", 48000, 20000, 2, 3);
+  AudioCodec c4(96, "A", 44100, 10000, 2, 3);
+  AudioCodec c5(96, "A", 44100, 20000, 1, 3);
+  AudioCodec c6(96, "A", 44100, 20000, 2, 1);
+  EXPECT_TRUE(c0 != c1);
+  EXPECT_TRUE(c0 != c2);
+  EXPECT_TRUE(c0 != c3);
+  EXPECT_TRUE(c0 != c4);
+  EXPECT_TRUE(c0 != c5);
+  EXPECT_TRUE(c0 != c6);
+
+  AudioCodec c7;
+  AudioCodec c8(0, "", 0, 0, 0, 0);
+  AudioCodec c9 = c0;
+  EXPECT_TRUE(c8 == c7);
+  EXPECT_TRUE(c9 != c7);
+  EXPECT_TRUE(c9 == c0);
+
+  AudioCodec c10(c0);
+  AudioCodec c11(c0);
+  AudioCodec c12(c0);
+  AudioCodec c13(c0);
+  c10.params["x"] = "abc";
+  c11.params["x"] = "def";
+  c12.params["y"] = "abc";
+  c13.params["x"] = "abc";
+  EXPECT_TRUE(c10 != c0);
+  EXPECT_TRUE(c11 != c0);
+  EXPECT_TRUE(c11 != c10);
+  EXPECT_TRUE(c12 != c0);
+  EXPECT_TRUE(c12 != c10);
+  EXPECT_TRUE(c12 != c11);
+  EXPECT_TRUE(c13 == c10);
+}
+
+TEST_F(CodecTest, TestAudioCodecMatches) {
+  // Test a codec with a static payload type.
+  AudioCodec c0(95, "A", 44100, 20000, 2, 3);
+  EXPECT_TRUE(c0.Matches(95, ""));
+  EXPECT_FALSE(c0.Matches(96, ""));
+  EXPECT_TRUE(c0.Matches(AudioCodec(95, "", 44100, 20000, 2, 0)));
+  EXPECT_TRUE(c0.Matches(AudioCodec(95, "", 44100, 20000, 0, 0)));
+  EXPECT_TRUE(c0.Matches(AudioCodec(95, "", 44100, 0, 0, 0)));
+  EXPECT_TRUE(c0.Matches(AudioCodec(95, "", 0, 0, 0, 0)));
+  EXPECT_FALSE(c0.Matches(AudioCodec(96, "", 44100, 20000, 2, 0)));
+  EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 55100, 20000, 2, 0)));
+  EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 44100, 30000, 2, 0)));
+  EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 44100, 20000, 1, 0)));
+  EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 55100, 30000, 1, 0)));
+
+  // Test a codec with a dynamic payload type.
+  AudioCodec c1(96, "A", 44100, 20000, 2, 3);
+  EXPECT_TRUE(c1.Matches(96, "A"));
+  EXPECT_TRUE(c1.Matches(97, "A"));
+  EXPECT_TRUE(c1.Matches(96, "a"));
+  EXPECT_TRUE(c1.Matches(97, "a"));
+  EXPECT_FALSE(c1.Matches(96, ""));
+  EXPECT_FALSE(c1.Matches(95, "A"));
+  EXPECT_TRUE(c1.Matches(AudioCodec(96, "A", 0, 0, 0, 0)));
+  EXPECT_FALSE(c1.Matches(AudioCodec(96, "", 44100, 20000, 2, 0)));
+  EXPECT_FALSE(c1.Matches(AudioCodec(96, "A", 55100, 30000, 1, 0)));
+
+  // Test a codec with a dynamic payload type, and auto bitrate.
+  AudioCodec c2(97, "A", 16000, 0, 1, 3);
+  // Use default bitrate.
+  EXPECT_TRUE(c2.Matches(AudioCodec(97, "A", 16000, 0, 1, 0)));
+  EXPECT_TRUE(c2.Matches(AudioCodec(97, "A", 16000, 0, 0, 0)));
+  // Use explicit bitrate.
+  EXPECT_TRUE(c2.Matches(AudioCodec(97, "A", 16000, 32000, 1, 0)));
+  // Backward compatibility with clients that might send "-1" (for default).
+  EXPECT_TRUE(c2.Matches(AudioCodec(97, "A", 16000, -1, 1, 0)));
+}
+
+TEST_F(CodecTest, TestVideoCodecOperators) {
+  VideoCodec c0(96, "V", 320, 200, 30, 3);
+  VideoCodec c1(95, "V", 320, 200, 30, 3);
+  VideoCodec c2(96, "x", 320, 200, 30, 3);
+  VideoCodec c3(96, "V", 120, 200, 30, 3);
+  VideoCodec c4(96, "V", 320, 100, 30, 3);
+  VideoCodec c5(96, "V", 320, 200, 10, 3);
+  VideoCodec c6(96, "V", 320, 200, 30, 1);
+  EXPECT_TRUE(c0 != c1);
+  EXPECT_TRUE(c0 != c2);
+  EXPECT_TRUE(c0 != c3);
+  EXPECT_TRUE(c0 != c4);
+  EXPECT_TRUE(c0 != c5);
+  EXPECT_TRUE(c0 != c6);
+
+  VideoCodec c7;
+  VideoCodec c8(0, "", 0, 0, 0, 0);
+  VideoCodec c9 = c0;
+  EXPECT_TRUE(c8 == c7);
+  EXPECT_TRUE(c9 != c7);
+  EXPECT_TRUE(c9 == c0);
+
+  VideoCodec c10(c0);
+  VideoCodec c11(c0);
+  VideoCodec c12(c0);
+  VideoCodec c13(c0);
+  c10.params["x"] = "abc";
+  c11.params["x"] = "def";
+  c12.params["y"] = "abc";
+  c13.params["x"] = "abc";
+  EXPECT_TRUE(c10 != c0);
+  EXPECT_TRUE(c11 != c0);
+  EXPECT_TRUE(c11 != c10);
+  EXPECT_TRUE(c12 != c0);
+  EXPECT_TRUE(c12 != c10);
+  EXPECT_TRUE(c12 != c11);
+  EXPECT_TRUE(c13 == c10);
+}
+
+TEST_F(CodecTest, TestVideoCodecMatches) {
+  // Test a codec with a static payload type.
+  VideoCodec c0(95, "V", 320, 200, 30, 3);
+  EXPECT_TRUE(c0.Matches(95, ""));
+  EXPECT_FALSE(c0.Matches(96, ""));
+  EXPECT_TRUE(c0.Matches(VideoCodec(95, "", 640, 400, 15, 0)));
+  EXPECT_FALSE(c0.Matches(VideoCodec(96, "", 320, 200, 30, 0)));
+
+  // Test a codec with a dynamic payload type.
+  VideoCodec c1(96, "V", 320, 200, 30, 3);
+  EXPECT_TRUE(c1.Matches(96, "V"));
+  EXPECT_TRUE(c1.Matches(97, "V"));
+  EXPECT_TRUE(c1.Matches(96, "v"));
+  EXPECT_TRUE(c1.Matches(97, "v"));
+  EXPECT_FALSE(c1.Matches(96, ""));
+  EXPECT_FALSE(c1.Matches(95, "V"));
+  EXPECT_TRUE(c1.Matches(VideoCodec(96, "V", 640, 400, 15, 0)));
+  EXPECT_FALSE(c1.Matches(VideoCodec(96, "", 320, 200, 30, 0)));
+}
+
+TEST_F(CodecTest, TestVideoEncoderConfigOperators) {
+  VideoEncoderConfig c1(VideoCodec(
+      96, "SVC", 320, 200, 30, 3), 1, 2);
+  VideoEncoderConfig c2(VideoCodec(
+      95, "SVC", 320, 200, 30, 3), 1, 2);
+  VideoEncoderConfig c3(VideoCodec(
+      96, "xxx", 320, 200, 30, 3), 1, 2);
+  VideoEncoderConfig c4(VideoCodec(
+      96, "SVC", 120, 200, 30, 3), 1, 2);
+  VideoEncoderConfig c5(VideoCodec(
+      96, "SVC", 320, 100, 30, 3), 1, 2);
+  VideoEncoderConfig c6(VideoCodec(
+      96, "SVC", 320, 200, 10, 3), 1, 2);
+  VideoEncoderConfig c7(VideoCodec(
+      96, "SVC", 320, 200, 30, 1), 1, 2);
+  VideoEncoderConfig c8(VideoCodec(
+      96, "SVC", 320, 200, 30, 3), 0, 2);
+  VideoEncoderConfig c9(VideoCodec(
+      96, "SVC", 320, 200, 30, 3), 1, 1);
+  EXPECT_TRUE(c1 != c2);
+  EXPECT_TRUE(c1 != c2);
+  EXPECT_TRUE(c1 != c3);
+  EXPECT_TRUE(c1 != c4);
+  EXPECT_TRUE(c1 != c5);
+  EXPECT_TRUE(c1 != c6);
+  EXPECT_TRUE(c1 != c7);
+  EXPECT_TRUE(c1 != c8);
+  EXPECT_TRUE(c1 != c9);
+
+  VideoEncoderConfig c10;
+  VideoEncoderConfig c11(VideoCodec(
+      0, "", 0, 0, 0, 0));
+  VideoEncoderConfig c12(VideoCodec(
+      0, "", 0, 0, 0, 0),
+      VideoEncoderConfig::kDefaultMaxThreads,
+      VideoEncoderConfig::kDefaultCpuProfile);
+  VideoEncoderConfig c13 = c1;
+  VideoEncoderConfig c14(VideoCodec(
+      0, "", 0, 0, 0, 0), 0, 0);
+
+  EXPECT_TRUE(c11 == c10);
+  EXPECT_TRUE(c12 == c10);
+  EXPECT_TRUE(c13 != c10);
+  EXPECT_TRUE(c13 == c1);
+  EXPECT_TRUE(c14 != c11);
+  EXPECT_TRUE(c14 != c12);
+}
diff --git a/talk/session/phone/currentspeakermonitor.cc b/talk/session/phone/currentspeakermonitor.cc
index bfc20a2..a148b7a 100644
--- a/talk/session/phone/currentspeakermonitor.cc
+++ b/talk/session/phone/currentspeakermonitor.cc
@@ -56,8 +56,8 @@
   if (!started_) {
     call_->SignalAudioMonitor.connect(
         this, &CurrentSpeakerMonitor::OnAudioMonitor);
-    call_->SignalMediaSourcesUpdate.connect(
-        this, &CurrentSpeakerMonitor::OnMediaSourcesUpdate);
+    call_->SignalMediaStreamsUpdate.connect(
+        this, &CurrentSpeakerMonitor::OnMediaStreamsUpdate);
 
     started_ = true;
   }
@@ -66,7 +66,7 @@
 void CurrentSpeakerMonitor::Stop() {
   if (started_) {
     call_->SignalAudioMonitor.disconnect(this);
-    call_->SignalMediaSourcesUpdate.disconnect(this);
+    call_->SignalMediaStreamsUpdate.disconnect(this);
 
     started_ = false;
     ssrc_to_speaking_state_map_.clear();
@@ -106,8 +106,9 @@
        state_it != ssrc_to_speaking_state_map_.end(); ++state_it) {
     bool is_previous_speaker = current_speaker_ssrc_ == state_it->first;
 
-    // This uses a state machine in order to gradually identify members as
-    // having started or stopped speaking.
+    // This uses a state machine in order to gradually identify
+    // members as having started or stopped speaking. Matches the
+    // algorithm used by the hangouts js code.
 
     std::map<uint32, int>::const_iterator level_it =
         active_ssrc_to_level_map.find(state_it->first);
@@ -186,21 +187,20 @@
   }
 }
 
-void CurrentSpeakerMonitor::OnMediaSourcesUpdate(Call* call,
+void CurrentSpeakerMonitor::OnMediaStreamsUpdate(Call* call,
                                                  Session* session,
-                                                 const MediaSources& sources) {
+                                                 const MediaStreams& added,
+                                                 const MediaStreams& removed) {
   if (call == call_ && session == session_) {
-    // Update the speaking state map based on new or removed sources.
-    NamedSources::const_iterator it;
-    for (it = sources.audio().begin(); it != sources.audio().end(); it++) {
-      if (it->ssrc_set) {
-        if (it->removed) {
-          ssrc_to_speaking_state_map_.erase(it->ssrc);
-        } else if (ssrc_to_speaking_state_map_.find(it->ssrc) ==
-            ssrc_to_speaking_state_map_.begin()) {
-          ssrc_to_speaking_state_map_[it->ssrc] = SS_NOT_SPEAKING;
-        }
-      }
+    // Update the speaking state map based on added and removed streams.
+    for (std::vector<cricket::StreamParams>::const_iterator
+           it = removed.video().begin(); it != removed.video().end(); ++it) {
+      ssrc_to_speaking_state_map_.erase(it->first_ssrc());
+    }
+
+    for (std::vector<cricket::StreamParams>::const_iterator
+           it = added.video().begin(); it != added.video().end(); ++it) {
+      ssrc_to_speaking_state_map_[it->first_ssrc()] = SS_NOT_SPEAKING;
     }
   }
 }
diff --git a/talk/session/phone/currentspeakermonitor.h b/talk/session/phone/currentspeakermonitor.h
index 84207fb..7871a82 100644
--- a/talk/session/phone/currentspeakermonitor.h
+++ b/talk/session/phone/currentspeakermonitor.h
@@ -42,7 +42,7 @@
 class Call;
 class Session;
 struct AudioInfo;
-struct MediaSources;
+struct MediaStreams;
 
 // Note that the call's audio monitor must be started before this is started.
 // It's recommended that the audio monitor be started with a 100 ms period.
@@ -68,9 +68,10 @@
 
  private:
   void OnAudioMonitor(Call* call, const AudioInfo& info);
-  void OnMediaSourcesUpdate(Call* call,
+  void OnMediaStreamsUpdate(Call* call,
                             Session* session,
-                            const MediaSources& sources);
+                            const MediaStreams& added,
+                            const MediaStreams& removed);
 
   // These are states that a participant will pass through so that we gradually
   // recognize that they have started and stopped speaking.  This avoids
diff --git a/talk/session/phone/currentspeakermonitor_unittest.cc b/talk/session/phone/currentspeakermonitor_unittest.cc
new file mode 100644
index 0000000..264fa25
--- /dev/null
+++ b/talk/session/phone/currentspeakermonitor_unittest.cc
@@ -0,0 +1,232 @@
+/*
+ * 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/thread.h"
+#include "talk/session/phone/call.h"
+#include "talk/session/phone/currentspeakermonitor.h"
+
+namespace cricket {
+
+static const uint32 kSsrc1 = 1001;
+static const uint32 kSsrc2 = 1002;
+static const uint32 kMinTimeBetweenSwitches = 10;
+// Due to limited system clock resolution, the CurrentSpeakerMonitor may
+// actually require more or less time between switches than that specified
+// in the call to set_min_time_between_switches.  To be safe, we sleep for
+// 90 ms more than the min time between switches before checking for a switch.
+// I am assuming system clocks do not have a coarser resolution than 90 ms.
+static const uint32 kSleepTimeBetweenSwitches = 100;
+
+class MockCall : public Call {
+ public:
+  MockCall() : Call(NULL) {}
+
+  void EmitAudioMonitor(const AudioInfo& info) {
+    SignalAudioMonitor(this, info);
+  }
+};
+
+class CurrentSpeakerMonitorTest : public testing::Test,
+    public sigslot::has_slots<> {
+ public:
+  CurrentSpeakerMonitorTest() {
+    call_ = new MockCall();
+    monitor_ = new CurrentSpeakerMonitor(call_, NULL);
+    // Shrink the minimum time betweeen switches to 10 ms so we don't have to
+    // slow down our tests.
+    monitor_->set_min_time_between_switches(kMinTimeBetweenSwitches);
+    monitor_->SignalUpdate.connect(this, &CurrentSpeakerMonitorTest::OnUpdate);
+    current_speaker_ = 0;
+    num_changes_ = 0;
+    monitor_->Start();
+  }
+
+  ~CurrentSpeakerMonitorTest() {
+    delete monitor_;
+    delete call_;
+  }
+
+ protected:
+  MockCall* call_;
+  CurrentSpeakerMonitor* monitor_;
+  int num_changes_;
+  uint32 current_speaker_;
+
+  void OnUpdate(CurrentSpeakerMonitor* monitor, uint32 current_speaker) {
+    current_speaker_ = current_speaker;
+    num_changes_++;
+  }
+};
+
+static void InitAudioInfo(AudioInfo* info, int input_level, int output_level) {
+  info->input_level = input_level;
+  info->output_level = output_level;
+}
+
+TEST_F(CurrentSpeakerMonitorTest, NoActiveStreams) {
+  AudioInfo info;
+  InitAudioInfo(&info, 0, 0);
+  call_->EmitAudioMonitor(info);
+
+  EXPECT_EQ(current_speaker_, 0U);
+  EXPECT_EQ(num_changes_, 0);
+}
+
+TEST_F(CurrentSpeakerMonitorTest, MultipleActiveStreams) {
+  AudioInfo info;
+  InitAudioInfo(&info, 0, 0);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+  call_->EmitAudioMonitor(info);
+
+  // No speaker recognized because the initial sample is treated as possibly
+  // just noise and disregarded.
+  EXPECT_EQ(current_speaker_, 0U);
+  EXPECT_EQ(num_changes_, 0);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+  call_->EmitAudioMonitor(info);
+
+  EXPECT_EQ(current_speaker_, kSsrc2);
+  EXPECT_EQ(num_changes_, 1);
+}
+
+TEST_F(CurrentSpeakerMonitorTest, RapidSpeakerChange) {
+  AudioInfo info;
+  InitAudioInfo(&info, 0, 0);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+  call_->EmitAudioMonitor(info);
+
+  EXPECT_EQ(current_speaker_, 0U);
+  EXPECT_EQ(num_changes_, 0);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+  call_->EmitAudioMonitor(info);
+
+  EXPECT_EQ(current_speaker_, kSsrc2);
+  EXPECT_EQ(num_changes_, 1);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 9));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 1));
+  call_->EmitAudioMonitor(info);
+
+  // We expect no speaker change because of the rapid change.
+  EXPECT_EQ(current_speaker_, kSsrc2);
+  EXPECT_EQ(num_changes_, 1);
+}
+
+TEST_F(CurrentSpeakerMonitorTest, SpeakerChange) {
+  AudioInfo info;
+  InitAudioInfo(&info, 0, 0);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+  call_->EmitAudioMonitor(info);
+
+  EXPECT_EQ(current_speaker_, 0U);
+  EXPECT_EQ(num_changes_, 0);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+  call_->EmitAudioMonitor(info);
+
+  EXPECT_EQ(current_speaker_, kSsrc2);
+  EXPECT_EQ(num_changes_, 1);
+
+  // Wait so the changes don't come so rapidly.
+  talk_base::Thread::SleepMs(kSleepTimeBetweenSwitches);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 9));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 1));
+  call_->EmitAudioMonitor(info);
+
+  EXPECT_EQ(current_speaker_, kSsrc1);
+  EXPECT_EQ(num_changes_, 2);
+}
+
+TEST_F(CurrentSpeakerMonitorTest, InterwordSilence) {
+  AudioInfo info;
+  InitAudioInfo(&info, 0, 0);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+  call_->EmitAudioMonitor(info);
+
+  EXPECT_EQ(current_speaker_, 0U);
+  EXPECT_EQ(num_changes_, 0);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+  call_->EmitAudioMonitor(info);
+
+  EXPECT_EQ(current_speaker_, kSsrc2);
+  EXPECT_EQ(num_changes_, 1);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+  call_->EmitAudioMonitor(info);
+
+  EXPECT_EQ(current_speaker_, kSsrc2);
+  EXPECT_EQ(num_changes_, 1);
+
+  // Wait so the changes don't come so rapidly.
+  talk_base::Thread::SleepMs(kSleepTimeBetweenSwitches);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 0));
+  call_->EmitAudioMonitor(info);
+
+  // Current speaker shouldn't have changed because we treat this as an inter-
+  // word silence.
+  EXPECT_EQ(current_speaker_, kSsrc2);
+  EXPECT_EQ(num_changes_, 1);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 0));
+  call_->EmitAudioMonitor(info);
+
+  // Current speaker shouldn't have changed because we treat this as an inter-
+  // word silence.
+  EXPECT_EQ(current_speaker_, kSsrc2);
+  EXPECT_EQ(num_changes_, 1);
+
+  info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+  info.active_streams.push_back(std::make_pair(kSsrc2, 0));
+  call_->EmitAudioMonitor(info);
+
+  // At this point, we should have concluded that SSRC2 stopped speaking.
+  EXPECT_EQ(current_speaker_, kSsrc1);
+  EXPECT_EQ(num_changes_, 2);
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/devicemanager.cc b/talk/session/phone/devicemanager.cc
index 6188461..67dfe41 100644
--- a/talk/session/phone/devicemanager.cc
+++ b/talk/session/phone/devicemanager.cc
@@ -27,40 +27,6 @@
 
 #include "talk/session/phone/devicemanager.h"
 
-#if WIN32
-#include <atlbase.h>
-#include <dbt.h>
-#include <strmif.h>  // must come before ks.h
-#include <ks.h>
-#include <ksmedia.h>
-#define INITGUID  // For PKEY_AudioEndpoint_GUID
-#include <mmdeviceapi.h>
-#include <mmsystem.h>
-#include <functiondiscoverykeys_devpkey.h>
-#include <uuids.h>
-#include "talk/base/win32.h"  // ToUtf8
-#include "talk/base/win32window.h"
-#elif OSX
-#include <CoreAudio/CoreAudio.h>
-#include <QuickTime/QuickTime.h>
-#elif LINUX
-#include <libudev.h>
-#include <unistd.h>
-#include "talk/base/linux.h"
-#include "talk/base/fileutils.h"
-#include "talk/base/pathutils.h"
-#include "talk/base/physicalsocketserver.h"
-#include "talk/base/stream.h"
-#include "talk/session/phone/libudevsymboltable.h"
-#include "talk/session/phone/v4llookup.h"
-#if !defined(NO_SOUND_SYSTEM)
-#include "talk/sound/platformsoundsystem.h"
-#include "talk/sound/platformsoundsystemfactory.h"
-#include "talk/sound/sounddevicelocator.h"
-#include "talk/sound/soundsysteminterface.h"
-#endif  // !defined(NO_SOUND_SYSTEM)
-#endif
-
 #include "talk/base/logging.h"
 #include "talk/base/stringutils.h"
 #include "talk/base/thread.h"
@@ -70,135 +36,30 @@
 // Initialize to empty string.
 const char DeviceManagerInterface::kDefaultDeviceName[] = "";
 
-DeviceManagerInterface* DeviceManagerFactory::Create() {
-  return new DeviceManager();
-}
-
-#ifdef WIN32
-class DeviceWatcher : public talk_base::Win32Window {
- public:
-  explicit DeviceWatcher(DeviceManager* dm);
-  bool Start();
-  void Stop();
-
- private:
-  HDEVNOTIFY Register(REFGUID guid);
-  void Unregister(HDEVNOTIFY notify);
-  virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result);
-
-  DeviceManager* manager_;
-  HDEVNOTIFY audio_notify_;
-  HDEVNOTIFY video_notify_;
-};
-#elif defined(LINUX)
-class DeviceWatcher : private talk_base::Dispatcher {
- public:
-  explicit DeviceWatcher(DeviceManager* dm);
-  bool Start();
-  void Stop();
-
- private:
-  virtual uint32 GetRequestedEvents();
-  virtual void OnPreEvent(uint32 ff);
-  virtual void OnEvent(uint32 ff, int err);
-  virtual int GetDescriptor();
-  virtual bool IsDescriptorClosed();
-
-  DeviceManager* manager_;
-  LibUDevSymbolTable libudev_;
-  struct udev* udev_;
-  struct udev_monitor* udev_monitor_;
-  bool registered_;
-};
-#define LATE(sym) LATESYM_GET(LibUDevSymbolTable, &libudev_, sym)
-#elif defined(OSX)
-class DeviceWatcher {
- public:
-  explicit DeviceWatcher(DeviceManager* dm);
-  bool Start();
-  void Stop();
- private:
-  DeviceManager* manager_;
-  void* impl_;
-};
-#endif
-
-#if defined(CHROMEOS)
-static bool ShouldAudioDeviceBeIgnored(const std::string& device_name);
-#endif
-#if !defined(LINUX) && !defined(IOS)
-static bool ShouldVideoDeviceBeIgnored(const std::string& device_name);
-#endif
-#ifndef OSX
-static bool GetVideoDevices(std::vector<Device>* out);
-#endif
-#if WIN32
-static const wchar_t kFriendlyName[] = L"FriendlyName";
-static const wchar_t kDevicePath[] = L"DevicePath";
-static const char kUsbDevicePathPrefix[] = "\\\\?\\usb";
-static bool GetDevices(const CLSID& catid, std::vector<Device>* out);
-static bool GetCoreAudioDevices(bool input, std::vector<Device>* devs);
-static bool GetWaveDevices(bool input, std::vector<Device>* devs);
-#elif OSX
-static const int kVideoDeviceOpenAttempts = 3;
-static const UInt32 kAudioDeviceNameLength = 64;
-// Obj-C functions defined in devicemanager-mac.mm
-extern void* CreateDeviceWatcherCallback(DeviceManager* dm);
-extern void ReleaseDeviceWatcherCallback(void* impl);
-extern bool GetQTKitVideoDevices(std::vector<Device>* out);
-static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out);
-static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out);
-#endif
-
 DeviceManager::DeviceManager()
-    : initialized_(false),
-#if defined(WIN32)
-      need_couninitialize_(false),
-#endif
-      watcher_(new DeviceWatcher(this))
-#if defined(LINUX) && !defined(NO_SOUND_SYSTEM)
-      , sound_system_(new PlatformSoundSystemFactory())
-#endif
-    {
+    : initialized_(false) {
 }
 
 DeviceManager::~DeviceManager() {
-  if (initialized_) {
+  if (initialized()) {
     Terminate();
   }
-  delete watcher_;
 }
 
 bool DeviceManager::Init() {
-  if (!initialized_) {
-#if defined(WIN32)
-    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
-    need_couninitialize_ = SUCCEEDED(hr);
-    if (FAILED(hr)) {
-      LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr;
-      if (hr != RPC_E_CHANGED_MODE) {
-        return false;
-      }
-    }
-#endif
-    if (!watcher_->Start()) {
+  if (!initialized()) {
+    if (!watcher()->Start()) {
       return false;
     }
-    initialized_ = true;
+    set_initialized(true);
   }
   return true;
 }
 
 void DeviceManager::Terminate() {
-  if (initialized_) {
-    watcher_->Stop();
-#if defined(WIN32)
-    if (need_couninitialize_) {
-      CoUninitialize();
-      need_couninitialize_ = false;
-    }
-#endif
-    initialized_ = false;
+  if (initialized()) {
+    watcher()->Stop();
+    set_initialized(false);
   }
 }
 
@@ -218,11 +79,11 @@
 }
 
 bool DeviceManager::GetAudioInputDevices(std::vector<Device>* devices) {
-  return GetAudioDevicesByPlatform(true, devices);
+  return GetAudioDevices(true, devices);
 }
 
 bool DeviceManager::GetAudioOutputDevices(std::vector<Device>* devices) {
-  return GetAudioDevicesByPlatform(false, devices);
+  return GetAudioDevices(false, devices);
 }
 
 bool DeviceManager::GetAudioInputDevice(const std::string& name, Device* out) {
@@ -233,55 +94,20 @@
   return GetAudioDevice(false, name, out);
 }
 
-#ifdef OSX
-static bool FilterDevice(const Device& d) {
-  return ShouldVideoDeviceBeIgnored(d.name);
-}
-#endif
-
 bool DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
   devices->clear();
-#ifdef OSX
-  if (GetQTKitVideoDevices(devices)) {
-    // Now filter out any known incompatible devices
-    devices->erase(remove_if(devices->begin(), devices->end(), FilterDevice),
-                   devices->end());
-    return true;
-  }
+#if defined(ANDROID) || defined(IOS)
+  // TODO: Incomplete. Use ANDROID implementation for IOS
+  // to quiet compiler.
+  // On Android, we treat the camera(s) as a single device. Even if there are
+  // multiple cameras, that's abstracted away at a higher level.
+  Device dev("camera", "1");    // name and ID
+  devices->push_back(dev);
+#else
   return false;
-#else
-  return GetVideoDevices(devices);
 #endif
 }
 
-bool DeviceManager::GetDefaultVideoCaptureDevice(Device* device) {
-  bool ret = false;
-#if WIN32
-  // If there are multiple capture devices, we want the first USB one.
-  // This avoids issues with defaulting to virtual cameras or grabber cards.
-  std::vector<Device> devices;
-  ret = (GetVideoDevices(&devices) && !devices.empty());
-  if (ret) {
-    *device = devices[0];
-    for (size_t i = 0; i < devices.size(); ++i) {
-      if (strnicmp(devices[i].id.c_str(), kUsbDevicePathPrefix,
-                   ARRAY_SIZE(kUsbDevicePathPrefix) - 1) == 0) {
-        *device = devices[i];
-        break;
-      }
-    }
-  }
-#else
-  // We just return the first device.
-  std::vector<Device> devices;
-  ret = (GetVideoCaptureDevices(&devices) && !devices.empty());
-  if (ret) {
-    *device = devices[0];
-  }
-#endif
-  return ret;
-}
-
 bool DeviceManager::GetVideoCaptureDevice(const std::string& name,
                                           Device* out) {
   // If the name is empty, return the default device.
@@ -305,6 +131,25 @@
   return false;
 }
 
+bool DeviceManager::GetAudioDevices(bool input,
+                                    std::vector<Device>* devs) {
+  devs->clear();
+#ifdef ANDROID
+  // Under Android, we don't access the device file directly.
+  // Arbitrary use 0 for the mic and 1 for the output.
+  // These ids are used in MediaEngine::SetSoundDevices(in, out);
+  // The strings are for human consumption.
+  if (input) {
+      devs->push_back(Device("audiorecord", 0));
+  } else {
+      devs->push_back(Device("audiotrack", 1));
+  }
+  return true;
+#else
+  return false;
+#endif
+}
+
 bool DeviceManager::GetAudioDevice(bool is_input, const std::string& name,
                                    Device* out) {
   // If the name is empty, return the default device id.
@@ -329,695 +174,50 @@
   return ret;
 }
 
-bool DeviceManager::GetAudioDevicesByPlatform(bool input,
-                                              std::vector<Device>* devs) {
-  devs->clear();
-
-#if defined(LINUX) && !defined(NO_SOUND_SYSTEM)
-  if (!sound_system_.get()) {
-    return false;
-  }
-  SoundSystemInterface::SoundDeviceLocatorList list;
-  bool success;
-  if (input) {
-    success = sound_system_->EnumerateCaptureDevices(&list);
-  } else {
-    success = sound_system_->EnumeratePlaybackDevices(&list);
-  }
-  if (!success) {
-    LOG(LS_ERROR) << "Can't enumerate devices";
-    sound_system_.release();
-    return false;
-  }
-  // We have to start the index at 1 because GIPS VoiceEngine puts the default
-  // device at index 0, but Enumerate(Capture|Playback)Devices does not include
-  // a locator for the default device.
-  int index = 1;
-  for (SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin();
-       i != list.end();
-       ++i, ++index) {
-#if defined(CHROMEOS)
-    // On ChromeOS, we ignore ALSA surround and S/PDIF devices.
-    if (!ShouldAudioDeviceBeIgnored((*i)->device_name())) {
-#endif
-      devs->push_back(Device((*i)->name(), index));
-#if defined(CHROMEOS)
-    }
-#endif
-  }
-  SoundSystemInterface::ClearSoundDeviceLocatorList(&list);
-  sound_system_.release();
-  return true;
-
-#elif defined(WIN32)
-  if (talk_base::IsWindowsVistaOrLater()) {
-    return GetCoreAudioDevices(input, devs);
-  } else {
-    return GetWaveDevices(input, devs);
-  }
-
-#elif defined(OSX)
-  std::vector<AudioDeviceID> dev_ids;
-  bool ret = GetAudioDeviceIDs(input, &dev_ids);
+bool DeviceManager::GetDefaultVideoCaptureDevice(Device* device) {
+  bool ret = false;
+  // We just return the first device.
+  std::vector<Device> devices;
+  ret = (GetVideoCaptureDevices(&devices) && !devices.empty());
   if (ret) {
-    for (size_t i = 0; i < dev_ids.size(); ++i) {
-      std::string name;
-      if (GetAudioDeviceName(dev_ids[i], input, &name)) {
-        devs->push_back(Device(name, dev_ids[i]));
-      }
-    }
+    *device = devices[0];
   }
   return ret;
-
-#else
-  return false;
-#endif
 }
 
-#if defined(WIN32)
-bool GetVideoDevices(std::vector<Device>* devices) {
-  return GetDevices(CLSID_VideoInputDeviceCategory, devices);
-}
-
-bool GetDevices(const CLSID& catid, std::vector<Device>* devices) {
-  HRESULT hr;
-
-  // CComPtr is a scoped pointer that will be auto released when going
-  // out of scope. CoUninitialize must not be called before the
-  // release.
-  CComPtr<ICreateDevEnum> sys_dev_enum;
-  CComPtr<IEnumMoniker> cam_enum;
-  if (FAILED(hr = sys_dev_enum.CoCreateInstance(CLSID_SystemDeviceEnum)) ||
-      FAILED(hr = sys_dev_enum->CreateClassEnumerator(catid, &cam_enum, 0))) {
-    LOG(LS_ERROR) << "Failed to create device enumerator, hr="  << hr;
+bool DeviceManager::ShouldDeviceBeIgnored(const std::string& device_name,
+    const char* const exclusion_list[]) {
+  // If exclusion_list is empty return directly.
+  if (!exclusion_list)
     return false;
-  }
 
-  // Only enum devices if CreateClassEnumerator returns S_OK. If there are no
-  // devices available, S_FALSE will be returned, but enumMk will be NULL.
-  if (hr == S_OK) {
-    CComPtr<IMoniker> mk;
-    while (cam_enum->Next(1, &mk, NULL) == S_OK) {
-      CComPtr<IPropertyBag> bag;
-      if (SUCCEEDED(mk->BindToStorage(NULL, NULL,
-          __uuidof(bag), reinterpret_cast<void**>(&bag)))) {
-        CComVariant name, path;
-        std::string name_str, path_str;
-        if (SUCCEEDED(bag->Read(kFriendlyName, &name, 0)) &&
-            name.vt == VT_BSTR) {
-          name_str = talk_base::ToUtf8(name.bstrVal);
-          if (!ShouldVideoDeviceBeIgnored(name_str)) {
-            // Get the device id if one exists.
-            if (SUCCEEDED(bag->Read(kDevicePath, &path, 0)) &&
-                path.vt == VT_BSTR) {
-              path_str = talk_base::ToUtf8(path.bstrVal);
-            }
-
-            devices->push_back(Device(name_str, path_str));
-          }
-        }
-      }
-      mk = NULL;
+  int i = 0;
+  while (exclusion_list[i]) {
+    if (strnicmp(device_name.c_str(), exclusion_list[i],
+        strlen(exclusion_list[i])) == 0) {
+      LOG(LS_INFO) << "Ignoring device " << device_name;
+      return true;
     }
+    ++i;
   }
-
-  return true;
-}
-
-HRESULT GetStringProp(IPropertyStore* bag, PROPERTYKEY key, std::string* out) {
-  out->clear();
-  PROPVARIANT var;
-  PropVariantInit(&var);
-
-  HRESULT hr = bag->GetValue(key, &var);
-  if (SUCCEEDED(hr)) {
-    if (var.pwszVal)
-      *out = talk_base::ToUtf8(var.pwszVal);
-    else
-      hr = E_FAIL;
-  }
-
-  PropVariantClear(&var);
-  return hr;
-}
-
-// Adapted from http://msdn.microsoft.com/en-us/library/dd370812(v=VS.85).aspx
-HRESULT CricketDeviceFromImmDevice(IMMDevice* device, Device* out) {
-  CComPtr<IPropertyStore> props;
-
-  HRESULT hr = device->OpenPropertyStore(STGM_READ, &props);
-  if (FAILED(hr)) {
-    return hr;
-  }
-
-  // Get the endpoint's name and id.
-  std::string name, guid;
-  hr = GetStringProp(props, PKEY_Device_FriendlyName, &name);
-  if (SUCCEEDED(hr)) {
-    hr = GetStringProp(props, PKEY_AudioEndpoint_GUID, &guid);
-
-    if (SUCCEEDED(hr)) {
-      out->name = name;
-      out->id = guid;
-    }
-  }
-  return hr;
-}
-
-bool GetCoreAudioDevices(bool input, std::vector<Device>* devs) {
-  HRESULT hr = S_OK;
-  CComPtr<IMMDeviceEnumerator> enumerator;
-
-  hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
-      __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&enumerator));
-  if (SUCCEEDED(hr)) {
-    CComPtr<IMMDeviceCollection> devices;
-    hr = enumerator->EnumAudioEndpoints((input ? eCapture : eRender),
-                                        DEVICE_STATE_ACTIVE, &devices);
-    if (SUCCEEDED(hr)) {
-      unsigned int count;
-      hr = devices->GetCount(&count);
-
-      if (SUCCEEDED(hr)) {
-        for (unsigned int i = 0; i < count; i++) {
-          CComPtr<IMMDevice> device;
-
-          // Get pointer to endpoint number i.
-          hr = devices->Item(i, &device);
-          if (FAILED(hr)) {
-            break;
-          }
-
-          Device dev;
-          hr = CricketDeviceFromImmDevice(device, &dev);
-          if (SUCCEEDED(hr)) {
-            devs->push_back(dev);
-          } else {
-            LOG(LS_WARNING) << "Unable to query IMM Device, skipping.  HR="
-                            << hr;
-            hr = S_FALSE;
-          }
-        }
-      }
-    }
-  }
-
-  if (FAILED(hr)) {
-    LOG(LS_WARNING) << "GetCoreAudioDevices failed with hr " << hr;
-    return false;
-  }
-  return true;
-}
-
-bool GetWaveDevices(bool input, std::vector<Device>* devs) {
-  // Note, we don't use the System Device Enumerator interface here since it
-  // adds lots of pseudo-devices to the list, such as DirectSound and Wave
-  // variants of the same device.
-  if (input) {
-    int num_devs = waveInGetNumDevs();
-    for (int i = 0; i < num_devs; ++i) {
-      WAVEINCAPS caps;
-      if (waveInGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
-          caps.wChannels > 0) {
-        devs->push_back(Device(talk_base::ToUtf8(caps.szPname),
-                               talk_base::ToString(i)));
-      }
-    }
-  } else {
-    int num_devs = waveOutGetNumDevs();
-    for (int i = 0; i < num_devs; ++i) {
-      WAVEOUTCAPS caps;
-      if (waveOutGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
-          caps.wChannels > 0) {
-        devs->push_back(Device(talk_base::ToUtf8(caps.szPname), i));
-      }
-    }
-  }
-  return true;
-}
-
-DeviceWatcher::DeviceWatcher(DeviceManager* manager)
-    : manager_(manager), audio_notify_(NULL), video_notify_(NULL) {
-}
-
-bool DeviceWatcher::Start() {
-  if (!Create(NULL, _T("libjingle DeviceWatcher Window"),
-              0, 0, 0, 0, 0, 0)) {
-    return false;
-  }
-
-  audio_notify_ = Register(KSCATEGORY_AUDIO);
-  if (!audio_notify_) {
-    Stop();
-    return false;
-  }
-
-  video_notify_ = Register(KSCATEGORY_VIDEO);
-  if (!video_notify_) {
-    Stop();
-    return false;
-  }
-
-  return true;
-}
-
-void DeviceWatcher::Stop() {
-  UnregisterDeviceNotification(video_notify_);
-  video_notify_ = NULL;
-  UnregisterDeviceNotification(audio_notify_);
-  audio_notify_ = NULL;
-  Destroy();
-}
-
-HDEVNOTIFY DeviceWatcher::Register(REFGUID guid) {
-  DEV_BROADCAST_DEVICEINTERFACE dbdi;
-  dbdi.dbcc_size = sizeof(dbdi);
-  dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
-  dbdi.dbcc_classguid = guid;
-  dbdi.dbcc_name[0] = '\0';
-  return RegisterDeviceNotification(handle(), &dbdi,
-                                    DEVICE_NOTIFY_WINDOW_HANDLE);
-}
-
-void DeviceWatcher::Unregister(HDEVNOTIFY handle) {
-  UnregisterDeviceNotification(handle);
-}
-
-bool DeviceWatcher::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
-                              LRESULT& result) {
-  if (uMsg == WM_DEVICECHANGE) {
-    if (wParam == DBT_DEVICEARRIVAL ||
-        wParam == DBT_DEVICEREMOVECOMPLETE) {
-      DEV_BROADCAST_DEVICEINTERFACE* dbdi =
-          reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(lParam);
-      if (dbdi->dbcc_classguid == KSCATEGORY_AUDIO ||
-        dbdi->dbcc_classguid == KSCATEGORY_VIDEO) {
-        manager_->OnDevicesChange();
-      }
-    }
-    result = 0;
-    return true;
-  }
-
   return false;
 }
-#elif defined(OSX)
-static bool GetAudioDeviceIDs(bool input,
-                              std::vector<AudioDeviceID>* out_dev_ids) {
-  UInt32 propsize;
-  OSErr err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
-                                           &propsize, NULL);
-  if (0 != err) {
-    LOG(LS_ERROR) << "Couldn't get information about property, "
-                  << "so no device list acquired.";
+
+bool DeviceManager::FilterDevices(std::vector<Device>* devices,
+    const char* const exclusion_list[]) {
+  if (!devices) {
     return false;
   }
 
-  size_t num_devices = propsize / sizeof(AudioDeviceID);
-  talk_base::scoped_array<AudioDeviceID> device_ids(
-      new AudioDeviceID[num_devices]);
-
-  err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
-                                 &propsize, device_ids.get());
-  if (0 != err) {
-    LOG(LS_ERROR) << "Failed to get device ids, "
-                  << "so no device listing acquired.";
-    return false;
-  }
-
-  for (size_t i = 0; i < num_devices; ++i) {
-    AudioDeviceID an_id = device_ids[i];
-    // find out the number of channels for this direction
-    // (input/output) on this device -
-    // we'll ignore anything with no channels.
-    err = AudioDeviceGetPropertyInfo(an_id, 0, input,
-                                     kAudioDevicePropertyStreams,
-                                     &propsize, NULL);
-    if (0 == err) {
-      unsigned num_channels = propsize / sizeof(AudioStreamID);
-      if (0 < num_channels) {
-        out_dev_ids->push_back(an_id);
-      }
+  for (std::vector<Device>::iterator it = devices->begin();
+       it != devices->end(); ) {
+    if (ShouldDeviceBeIgnored(it->name, exclusion_list)) {
+      it = devices->erase(it);
     } else {
-      LOG(LS_ERROR) << "No property info for stream property for device id "
-                    << an_id << "(is_input == " << input
-                    << "), so not including it in the list.";
+      ++it;
     }
   }
-
   return true;
 }
 
-static bool GetAudioDeviceName(AudioDeviceID id,
-                               bool input,
-                               std::string* out_name) {
-  UInt32 nameLength = kAudioDeviceNameLength;
-  char name[kAudioDeviceNameLength + 1];
-  OSErr err = AudioDeviceGetProperty(id, 0, input,
-                                     kAudioDevicePropertyDeviceName,
-                                     &nameLength, name);
-  if (0 != err) {
-    LOG(LS_ERROR) << "No name acquired for device id " << id;
-    return false;
-  }
-
-  *out_name = name;
-  return true;
-}
-
-DeviceWatcher::DeviceWatcher(DeviceManager* manager)
-    : manager_(manager), impl_(NULL) {
-}
-
-bool DeviceWatcher::Start() {
-  if (!impl_) {
-    impl_ = CreateDeviceWatcherCallback(manager_);
-  }
-  return impl_ != NULL;
-}
-
-void DeviceWatcher::Stop() {
-  if (impl_) {
-    ReleaseDeviceWatcherCallback(impl_);
-    impl_ = NULL;
-  }
-}
-
-#elif defined(LINUX)
-static const std::string kVideoMetaPathK2_4("/proc/video/dev/");
-static const std::string kVideoMetaPathK2_6("/sys/class/video4linux/");
-
-enum MetaType { M2_4, M2_6, NONE };
-
-static void ScanDeviceDirectory(const std::string& devdir,
-                                std::vector<Device>* devices) {
-  talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
-      talk_base::Filesystem::IterateDirectory());
-
-  if (directoryIterator->Iterate(talk_base::Pathname(devdir))) {
-    do {
-      std::string filename = directoryIterator->Name();
-      std::string device_name = devdir + filename;
-      if (!directoryIterator->IsDots()) {
-        if (filename.find("video") == 0 &&
-            V4LLookup::IsV4L2Device(device_name)) {
-          devices->push_back(Device(device_name, device_name));
-        }
-      }
-    } while (directoryIterator->Next());
-  }
-}
-
-static std::string GetVideoDeviceNameK2_6(const std::string& device_meta_path) {
-  std::string device_name;
-
-  talk_base::scoped_ptr<talk_base::FileStream> device_meta_stream(
-      talk_base::Filesystem::OpenFile(device_meta_path, "r"));
-
-  if (device_meta_stream.get() != NULL) {
-    if (device_meta_stream->ReadLine(&device_name) != talk_base::SR_SUCCESS) {
-      LOG(LS_ERROR) << "Failed to read V4L2 device meta " << device_meta_path;
-    }
-    device_meta_stream->Close();
-  }
-
-  return device_name;
-}
-
-static std::string Trim(const std::string& s, const std::string& drop = " \t") {
-  std::string::size_type first = s.find_first_not_of(drop);
-  std::string::size_type last  = s.find_last_not_of(drop);
-
-  if (first == std::string::npos || last == std::string::npos)
-    return std::string("");
-
-  return s.substr(first, last - first + 1);
-}
-
-static std::string GetVideoDeviceNameK2_4(const std::string& device_meta_path) {
-  talk_base::ConfigParser::MapVector all_values;
-
-  talk_base::ConfigParser config_parser;
-  talk_base::FileStream* file_stream =
-      talk_base::Filesystem::OpenFile(device_meta_path, "r");
-
-  if (file_stream == NULL) return "";
-
-  config_parser.Attach(file_stream);
-  config_parser.Parse(&all_values);
-
-  for (talk_base::ConfigParser::MapVector::iterator i = all_values.begin();
-      i != all_values.end(); ++i) {
-    talk_base::ConfigParser::SimpleMap::iterator device_name_i =
-        i->find("name");
-
-    if (device_name_i != i->end()) {
-      return device_name_i->second;
-    }
-  }
-
-  return "";
-}
-
-static std::string GetVideoDeviceName(MetaType meta,
-    const std::string& device_file_name) {
-  std::string device_meta_path;
-  std::string device_name;
-  std::string meta_file_path;
-
-  if (meta == M2_6) {
-    meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/name";
-
-    LOG(LS_INFO) << "Trying " + meta_file_path;
-    device_name = GetVideoDeviceNameK2_6(meta_file_path);
-
-    if (device_name.empty()) {
-      meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/model";
-
-      LOG(LS_INFO) << "Trying " << meta_file_path;
-      device_name = GetVideoDeviceNameK2_6(meta_file_path);
-    }
-  } else {
-    meta_file_path = kVideoMetaPathK2_4 + device_file_name;
-    LOG(LS_INFO) << "Trying " << meta_file_path;
-    device_name = GetVideoDeviceNameK2_4(meta_file_path);
-  }
-
-  if (device_name.empty()) {
-    device_name = "/dev/" + device_file_name;
-    LOG(LS_ERROR)
-      << "Device name not found, defaulting to device path " << device_name;
-  }
-
-  LOG(LS_INFO) << "Name for " << device_file_name << " is " << device_name;
-
-  return Trim(device_name);
-}
-
-static void ScanV4L2Devices(std::vector<Device>* devices) {
-  LOG(LS_INFO) << ("Enumerating V4L2 devices");
-
-  MetaType meta;
-  std::string metadata_dir;
-
-  talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
-      talk_base::Filesystem::IterateDirectory());
-
-  // Try and guess kernel version
-  if (directoryIterator->Iterate(kVideoMetaPathK2_6)) {
-    meta = M2_6;
-    metadata_dir = kVideoMetaPathK2_6;
-  } else if (directoryIterator->Iterate(kVideoMetaPathK2_4)) {
-    meta = M2_4;
-    metadata_dir = kVideoMetaPathK2_4;
-  } else {
-    meta = NONE;
-  }
-
-  if (meta != NONE) {
-    LOG(LS_INFO) << "V4L2 device metadata found at " << metadata_dir;
-
-    do {
-      std::string filename = directoryIterator->Name();
-
-      if (filename.find("video") == 0) {
-        std::string device_path = "/dev/" + filename;
-
-        if (V4LLookup::IsV4L2Device(device_path)) {
-          devices->push_back(
-              Device(GetVideoDeviceName(meta, filename), device_path));
-        }
-      }
-    } while (directoryIterator->Next());
-  } else {
-    LOG(LS_ERROR) << "Unable to detect v4l2 metadata directory";
-  }
-
-  if (devices->size() == 0) {
-    LOG(LS_INFO) << "Plan B. Scanning all video devices in /dev directory";
-    ScanDeviceDirectory("/dev/", devices);
-  }
-
-  LOG(LS_INFO) << "Total V4L2 devices found : " << devices->size();
-}
-
-static bool GetVideoDevices(std::vector<Device>* devices) {
-  ScanV4L2Devices(devices);
-  return true;
-}
-
-DeviceWatcher::DeviceWatcher(DeviceManager* dm)
-    : manager_(dm), udev_(NULL), udev_monitor_(NULL), registered_(false) {}
-
-bool DeviceWatcher::Start() {
-  // We deliberately return true in the failure paths here because libudev is
-  // not a critical component of a Linux system so it may not be present/usable,
-  // and we don't want to halt DeviceManager initialization in such a case.
-  if (!libudev_.Load()) {
-    LOG(LS_WARNING) << "libudev not present/usable; DeviceWatcher disabled";
-    return true;
-  }
-  udev_ = LATE(udev_new)();
-  if (!udev_) {
-    LOG_ERR(LS_ERROR) << "udev_new()";
-    return true;
-  }
-  // The second argument here is the event source. It can be either "kernel" or
-  // "udev", but "udev" is the only correct choice. Apps listen on udev and the
-  // udev daemon in turn listens on the kernel.
-  udev_monitor_ = LATE(udev_monitor_new_from_netlink)(udev_, "udev");
-  if (!udev_monitor_) {
-    LOG_ERR(LS_ERROR) << "udev_monitor_new_from_netlink()";
-    return true;
-  }
-  // We only listen for changes in the video devices. Audio devices are more or
-  // less unimportant because receiving device change notifications really only
-  // matters for broadcasting updated send/recv capabilities based on whether
-  // there is at least one device available, and almost all computers have at
-  // least one audio device. Also, PulseAudio device notifications don't come
-  // from the udev daemon, they come from the PulseAudio daemon, so we'd only
-  // want to listen for audio device changes from udev if using ALSA. For
-  // simplicity, we don't bother with any audio stuff at all.
-  if (LATE(udev_monitor_filter_add_match_subsystem_devtype)(udev_monitor_,
-                                                            "video4linux",
-                                                            NULL) < 0) {
-    LOG_ERR(LS_ERROR) << "udev_monitor_filter_add_match_subsystem_devtype()";
-    return true;
-  }
-  if (LATE(udev_monitor_enable_receiving)(udev_monitor_) < 0) {
-    LOG_ERR(LS_ERROR) << "udev_monitor_enable_receiving()";
-    return true;
-  }
-  static_cast<talk_base::PhysicalSocketServer*>(
-      talk_base::Thread::Current()->socketserver())->Add(this);
-  registered_ = true;
-  return true;
-}
-
-void DeviceWatcher::Stop() {
-  if (registered_) {
-    static_cast<talk_base::PhysicalSocketServer*>(
-        talk_base::Thread::Current()->socketserver())->Remove(this);
-    registered_ = false;
-  }
-  if (udev_monitor_) {
-    LATE(udev_monitor_unref)(udev_monitor_);
-    udev_monitor_ = NULL;
-  }
-  if (udev_) {
-    LATE(udev_unref)(udev_);
-    udev_ = NULL;
-  }
-  libudev_.Unload();
-}
-
-uint32 DeviceWatcher::GetRequestedEvents() {
-  return talk_base::DE_READ;
-}
-
-void DeviceWatcher::OnPreEvent(uint32 ff) {
-  // Nothing to do.
-}
-
-void DeviceWatcher::OnEvent(uint32 ff, int err) {
-  udev_device* device = LATE(udev_monitor_receive_device)(udev_monitor_);
-  if (!device) {
-    // Probably the socket connection to the udev daemon was terminated (perhaps
-    // the daemon crashed or is being restarted?).
-    LOG_ERR(LS_WARNING) << "udev_monitor_receive_device()";
-    // Stop listening to avoid potential livelock (an fd with EOF in it is
-    // always considered readable).
-    static_cast<talk_base::PhysicalSocketServer*>(
-        talk_base::Thread::Current()->socketserver())->Remove(this);
-    registered_ = false;
-    return;
-  }
-  // Else we read the device successfully.
-
-  // Since we already have our own filesystem-based device enumeration code, we
-  // simply re-enumerate rather than inspecting the device event.
-  LATE(udev_device_unref)(device);
-  manager_->OnDevicesChange();
-}
-
-int DeviceWatcher::GetDescriptor() {
-  return LATE(udev_monitor_get_fd)(udev_monitor_);
-}
-
-bool DeviceWatcher::IsDescriptorClosed() {
-  // If it is closed then we will just get an error in
-  // udev_monitor_receive_device and unregister, so we don't need to check for
-  // it separately.
-  return false;
-}
-
-#endif
-
-#if defined(CHROMEOS)
-// Checks if we want to ignore this audio device.
-static bool ShouldAudioDeviceBeIgnored(const std::string& device_name) {
-  static const char* const kFilteredAudioDevicesName[] =  {
-      "surround40:",
-      "surround41:",
-      "surround50:",
-      "surround51:",
-      "surround71:",
-      "iec958:"       // S/PDIF
-  };
-  for (int i = 0; i < ARRAY_SIZE(kFilteredAudioDevicesName); ++i) {
-    if (0 == device_name.find(kFilteredAudioDevicesName[i])) {
-      LOG(LS_INFO) << "Ignoring device " << device_name;
-      return true;
-    }
-  }
-  return false;
-}
-#endif
-
-// TODO: Try to get hold of a copy of Final Cut to understand why we
-//               crash while scanning their components on OS X.
-#if !defined(LINUX) && !defined(IOS)
-static bool ShouldVideoDeviceBeIgnored(const std::string& device_name) {
-  static const char* const kFilteredVideoDevicesName[] =  {
-      "Google Camera Adapter",   // Our own magiccams
-#ifdef WIN32
-      "Asus virtual Camera",     // Bad Asus desktop virtual cam
-      "Bluetooth Video",         // Bad Sony viao bluetooth sharing driver
-#elif OSX
-      "DVCPRO HD",               // Final cut
-      "Sonix SN9C201p",          // Crashes in OpenAComponent and CloseComponent
-#endif
-  };
-
-  for (int i = 0; i < ARRAY_SIZE(kFilteredVideoDevicesName); ++i) {
-    if (strnicmp(device_name.c_str(), kFilteredVideoDevicesName[i],
-        strlen(kFilteredVideoDevicesName[i])) == 0) {
-      LOG(LS_INFO) << "Ignoring device " << device_name;
-      return true;
-    }
-  }
-  return false;
-}
-#endif
-
-};  // namespace cricket
+}  // namespace cricket
diff --git a/talk/session/phone/devicemanager.h b/talk/session/phone/devicemanager.h
index d846ee5..3cf3554 100644
--- a/talk/session/phone/devicemanager.h
+++ b/talk/session/phone/devicemanager.h
@@ -31,26 +31,12 @@
 #include <string>
 #include <vector>
 
+#include "talk/base/scoped_ptr.h"
 #include "talk/base/sigslot.h"
 #include "talk/base/stringencode.h"
 
-#ifdef BUILD_WITH_CHROMIUM
-// The SoundSystem related code refers to some definitions that are not
-// available in chromium. (LS_VERBOSE, DISALLOW_ASSIGN etc.)
-// For now, disable the sound system code from devicemanager.h/cc.
-// TODO: Split the DeviceManager implemenations out of
-// devicemanager.h/cc so that we can exclude the DeviceManager impls from
-// libjingle build when they are not needed.
-#define NO_SOUND_SYSTEM
-#endif
-#if defined(LINUX) && !defined(NO_SOUND_SYSTEM)
-#include "talk/sound/soundsystemfactory.h"
-#endif
-
 namespace cricket {
 
-class DeviceWatcher;
-
 // Used to represent an audio or video capture or render device.
 struct Device {
   Device() {}
@@ -93,6 +79,14 @@
   static const char kDefaultDeviceName[];
 };
 
+class DeviceWatcher {
+ public:
+  explicit DeviceWatcher(DeviceManagerInterface* dm) {}
+  virtual ~DeviceWatcher() {}
+  virtual bool Start() { return true; }
+  virtual void Stop() {}
+};
+
 class DeviceManagerFactory {
  public:
   static DeviceManagerInterface* Create();
@@ -122,25 +116,28 @@
   virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
   virtual bool GetVideoCaptureDevice(const std::string& name, Device* out);
 
+  // The exclusion_list MUST be a NULL terminated list.
+  static bool FilterDevices(std::vector<Device>* devices,
+      const char* const exclusion_list[]);
   bool initialized() const { return initialized_; }
-  void OnDevicesChange() { SignalDevicesChange(); }
 
  protected:
+  virtual bool GetAudioDevices(bool input, std::vector<Device>* devs);
   virtual bool GetAudioDevice(bool is_input, const std::string& name,
                               Device* out);
   virtual bool GetDefaultVideoCaptureDevice(Device* device);
 
- private:
-  bool GetAudioDevicesByPlatform(bool input, std::vector<Device>* devs);
+  void set_initialized(bool initialized) { initialized_ = initialized; }
 
+  void set_watcher(DeviceWatcher* watcher) { watcher_.reset(watcher); }
+  DeviceWatcher* watcher() { return watcher_.get(); }
+
+ private:
+  // The exclusion_list MUST be a NULL terminated list.
+  static bool ShouldDeviceBeIgnored(const std::string& device_name,
+      const char* const exclusion_list[]);
   bool initialized_;
-#ifdef WIN32
-  bool need_couninitialize_;
-#endif
-  DeviceWatcher* watcher_;
-#if defined(LINUX) && !defined(NO_SOUND_SYSTEM)
-  SoundSystemHandle sound_system_;
-#endif
+  talk_base::scoped_ptr<DeviceWatcher> watcher_;
 };
 
 }  // namespace cricket
diff --git a/talk/session/phone/devicemanager_unittest.cc b/talk/session/phone/devicemanager_unittest.cc
new file mode 100644
index 0000000..a64eab9
--- /dev/null
+++ b/talk/session/phone/devicemanager_unittest.cc
@@ -0,0 +1,312 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <objbase.h>
+#endif
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/session/phone/devicemanager.h"
+#include "talk/session/phone/v4llookup.h"
+
+#ifdef LINUX
+// TODO: Figure out why this doesn't compile on Windows.
+#include "talk/base/fileutils_mock.h"
+#endif  // LINUX
+
+#include "talk/session/phone/devicemanager.h"
+
+using talk_base::Pathname;
+using talk_base::FileTimeType;
+using talk_base::scoped_ptr;
+using cricket::Device;
+using cricket::DeviceManager;
+using cricket::DeviceManagerFactory;
+using cricket::DeviceManagerInterface;
+
+// Test that we startup/shutdown properly.
+TEST(DeviceManagerTest, StartupShutdown) {
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  EXPECT_TRUE(dm->Init());
+  dm->Terminate();
+}
+
+// Test CoInitEx behavior
+#ifdef WIN32
+TEST(DeviceManagerTest, CoInitialize) {
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  std::vector<Device> devices;
+  // Ensure that calls to video device work if COM is not yet initialized.
+  EXPECT_TRUE(dm->Init());
+  EXPECT_TRUE(dm->GetVideoCaptureDevices(&devices));
+  dm->Terminate();
+  // Ensure that the ref count is correct.
+  EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+  CoUninitialize();
+  // Ensure that Init works in COINIT_APARTMENTTHREADED setting.
+  EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
+  EXPECT_TRUE(dm->Init());
+  dm->Terminate();
+  CoUninitialize();
+  // Ensure that the ref count is correct.
+  EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
+  CoUninitialize();
+  // Ensure that Init works in COINIT_MULTITHREADED setting.
+  EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+  EXPECT_TRUE(dm->Init());
+  dm->Terminate();
+  CoUninitialize();
+  // Ensure that the ref count is correct.
+  EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+  CoUninitialize();
+}
+#endif
+
+// Test enumerating devices (although we may not find any).
+TEST(DeviceManagerTest, GetDevices) {
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  std::vector<Device> audio_ins, audio_outs, video_ins;
+  std::vector<cricket::Device> video_in_devs;
+  cricket::Device def_video;
+  EXPECT_TRUE(dm->Init());
+  EXPECT_TRUE(dm->GetAudioInputDevices(&audio_ins));
+  EXPECT_TRUE(dm->GetAudioOutputDevices(&audio_outs));
+  EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+  EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_in_devs));
+  EXPECT_EQ(video_ins.size(), video_in_devs.size());
+  // If we have any video devices, we should be able to pick a default.
+  EXPECT_TRUE(dm->GetVideoCaptureDevice(
+      cricket::DeviceManagerInterface::kDefaultDeviceName, &def_video)
+      != video_ins.empty());
+}
+
+// Test that we return correct ids for default and bogus devices.
+TEST(DeviceManagerTest, GetAudioDeviceIds) {
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  Device device;
+  EXPECT_TRUE(dm->Init());
+  EXPECT_TRUE(dm->GetAudioInputDevice(
+      cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+  EXPECT_EQ("-1", device.id);
+  EXPECT_TRUE(dm->GetAudioOutputDevice(
+      cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+  EXPECT_EQ("-1", device.id);
+  EXPECT_FALSE(dm->GetAudioInputDevice("_NOT A REAL DEVICE_", &device));
+  EXPECT_FALSE(dm->GetAudioOutputDevice("_NOT A REAL DEVICE_", &device));
+}
+
+// Test that we get the video capture device by name properly.
+TEST(DeviceManagerTest, GetVideoDeviceIds) {
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  Device device;
+  EXPECT_TRUE(dm->Init());
+  EXPECT_FALSE(dm->GetVideoCaptureDevice("_NOT A REAL DEVICE_", &device));
+  std::vector<Device> video_ins;
+  EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+  if (!video_ins.empty()) {
+    // Get the default device with the parameter kDefaultDeviceName.
+    EXPECT_TRUE(dm->GetVideoCaptureDevice(
+        cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+
+    // Get the first device with the parameter video_ins[0].name.
+    EXPECT_TRUE(dm->GetVideoCaptureDevice(video_ins[0].name, &device));
+    EXPECT_EQ(device.name, video_ins[0].name);
+    EXPECT_EQ(device.id, video_ins[0].id);
+  }
+}
+
+TEST(DeviceManagerTest, VerifyDevicesListsAreCleared) {
+  const std::string imaginary("_NOT A REAL DEVICE_");
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  std::vector<Device> audio_ins, audio_outs, video_ins;
+  audio_ins.push_back(Device(imaginary, imaginary));
+  audio_outs.push_back(Device(imaginary, imaginary));
+  video_ins.push_back(Device(imaginary, imaginary));
+  EXPECT_TRUE(dm->Init());
+  EXPECT_TRUE(dm->GetAudioInputDevices(&audio_ins));
+  EXPECT_TRUE(dm->GetAudioOutputDevices(&audio_outs));
+  EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+  for (size_t i = 0; i < audio_ins.size(); ++i) {
+    EXPECT_NE(imaginary, audio_ins[i].name);
+  }
+  for (size_t i = 0; i < audio_outs.size(); ++i) {
+    EXPECT_NE(imaginary, audio_outs[i].name);
+  }
+  for (size_t i = 0; i < video_ins.size(); ++i) {
+    EXPECT_NE(imaginary, video_ins[i].name);
+  }
+}
+
+static bool CompareDeviceList(std::vector<Device>& devices,
+    const char* const device_list[], int list_size) {
+  if (list_size != static_cast<int>(devices.size())) {
+    return false;
+  }
+  for (int i = 0; i < list_size; ++i) {
+    if (devices[i].name.compare(device_list[i]) != 0) {
+      return false;
+    }
+  }
+  return true;
+}
+
+TEST(DeviceManagerTest, VerifyFilterDevices) {
+  static const char* const kTotalDevicesName[] = {
+      "Google Camera Adapters are tons of fun.",
+      "device1",
+      "device2",
+      "device3",
+      "device4",
+      "device5",
+      "Google Camera Adapter 0",
+      "Google Camera Adapter 1",
+  };
+  static const char* const kFilteredDevicesName[] = {
+      "device2",
+      "device4",
+      "Google Camera Adapter",
+      NULL,
+  };
+  static const char* const kDevicesName[] = {
+      "device1",
+      "device3",
+      "device5",
+  };
+  std::vector<Device> devices;
+  for (int i = 0; i < ARRAY_SIZE(kTotalDevicesName); ++i) {
+    devices.push_back(Device(kTotalDevicesName[i], i));
+  }
+  EXPECT_TRUE(CompareDeviceList(devices, kTotalDevicesName,
+                                ARRAY_SIZE(kTotalDevicesName)));
+  // Return false if given NULL as the exclusion list.
+  EXPECT_TRUE(DeviceManager::FilterDevices(&devices, NULL));
+  // The devices should not change.
+  EXPECT_TRUE(CompareDeviceList(devices, kTotalDevicesName,
+                                ARRAY_SIZE(kTotalDevicesName)));
+  EXPECT_TRUE(DeviceManager::FilterDevices(&devices, kFilteredDevicesName));
+  EXPECT_TRUE(CompareDeviceList(devices, kDevicesName,
+                                ARRAY_SIZE(kDevicesName)));
+}
+
+#ifdef LINUX
+class FakeV4LLookup : public cricket::V4LLookup {
+ public:
+  explicit FakeV4LLookup(std::vector<std::string> device_paths)
+      : device_paths_(device_paths) {}
+
+ protected:
+  bool CheckIsV4L2Device(const std::string& device) {
+    return std::find(device_paths_.begin(), device_paths_.end(), device)
+        != device_paths_.end();
+  }
+
+ private:
+  std::vector<std::string> device_paths_;
+};
+
+TEST(DeviceManagerTest, GetVideoCaptureDevices_K2_6) {
+  std::vector<std::string> devices;
+  devices.push_back("/dev/video0");
+  devices.push_back("/dev/video5");
+  cricket::V4LLookup::SetV4LLookup(new FakeV4LLookup(devices));
+
+  std::vector<talk_base::FakeFileSystem::File> files;
+  files.push_back(talk_base::FakeFileSystem::File("/dev/video0", ""));
+  files.push_back(talk_base::FakeFileSystem::File("/dev/video5", ""));
+  files.push_back(talk_base::FakeFileSystem::File(
+      "/sys/class/video4linux/video0/name", "Video Device 1"));
+  files.push_back(talk_base::FakeFileSystem::File(
+      "/sys/class/video4linux/video1/model", "Bad Device"));
+  files.push_back(
+      talk_base::FakeFileSystem::File("/sys/class/video4linux/video5/model",
+                                      "Video Device 2"));
+  talk_base::FilesystemScope fs(new talk_base::FakeFileSystem(files));
+
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  std::vector<Device> video_ins;
+  EXPECT_TRUE(dm->Init());
+  EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+  EXPECT_EQ(2u, video_ins.size());
+  EXPECT_EQ("Video Device 1", video_ins.at(0).name);
+  EXPECT_EQ("Video Device 2", video_ins.at(1).name);
+}
+
+TEST(DeviceManagerTest, GetVideoCaptureDevices_K2_4) {
+  std::vector<std::string> devices;
+  devices.push_back("/dev/video0");
+  devices.push_back("/dev/video5");
+  cricket::V4LLookup::SetV4LLookup(new FakeV4LLookup(devices));
+
+  std::vector<talk_base::FakeFileSystem::File> files;
+  files.push_back(talk_base::FakeFileSystem::File("/dev/video0", ""));
+  files.push_back(talk_base::FakeFileSystem::File("/dev/video5", ""));
+  files.push_back(talk_base::FakeFileSystem::File(
+          "/proc/video/dev/video0",
+          "param1: value1\nname: Video Device 1\n param2: value2\n"));
+  files.push_back(talk_base::FakeFileSystem::File(
+          "/proc/video/dev/video1",
+          "param1: value1\nname: Bad Device\n param2: value2\n"));
+  files.push_back(talk_base::FakeFileSystem::File(
+          "/proc/video/dev/video5",
+          "param1: value1\nname:   Video Device 2\n param2: value2\n"));
+  talk_base::FilesystemScope fs(new talk_base::FakeFileSystem(files));
+
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  std::vector<Device> video_ins;
+  EXPECT_TRUE(dm->Init());
+  EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+  EXPECT_EQ(2u, video_ins.size());
+  EXPECT_EQ("Video Device 1", video_ins.at(0).name);
+  EXPECT_EQ("Video Device 2", video_ins.at(1).name);
+}
+
+TEST(DeviceManagerTest, GetVideoCaptureDevices_KUnknown) {
+  std::vector<std::string> devices;
+  devices.push_back("/dev/video0");
+  devices.push_back("/dev/video5");
+  cricket::V4LLookup::SetV4LLookup(new FakeV4LLookup(devices));
+
+  std::vector<talk_base::FakeFileSystem::File> files;
+  files.push_back(talk_base::FakeFileSystem::File("/dev/video0", ""));
+  files.push_back(talk_base::FakeFileSystem::File("/dev/video1", ""));
+  files.push_back(talk_base::FakeFileSystem::File("/dev/video5", ""));
+  talk_base::FilesystemScope fs(new talk_base::FakeFileSystem(files));
+
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  std::vector<Device> video_ins;
+  EXPECT_TRUE(dm->Init());
+  EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+  EXPECT_EQ(2u, video_ins.size());
+  EXPECT_EQ("/dev/video0", video_ins.at(0).name);
+  EXPECT_EQ("/dev/video5", video_ins.at(1).name);
+}
+#endif  // LINUX
diff --git a/talk/session/phone/dummydevicemanager.cc b/talk/session/phone/dummydevicemanager.cc
new file mode 100644
index 0000000..5c2d1c2
--- /dev/null
+++ b/talk/session/phone/dummydevicemanager.cc
@@ -0,0 +1,36 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/dummydevicemanager.h"
+
+namespace cricket {
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+  return new DummyDeviceManager();
+}
+
+};  // namespace cricket
diff --git a/talk/session/phone/dummydevicemanager_unittest.cc b/talk/session/phone/dummydevicemanager_unittest.cc
new file mode 100644
index 0000000..3fe734e
--- /dev/null
+++ b/talk/session/phone/dummydevicemanager_unittest.cc
@@ -0,0 +1,104 @@
+/*
+ * 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/session/phone/dummydevicemanager.h"
+
+using cricket::Device;
+using cricket::DummyDeviceManager;
+
+// Test that we startup/shutdown properly.
+TEST(DummyDeviceManagerTest, StartupShutdown) {
+  DummyDeviceManager dm;
+  EXPECT_TRUE(dm.Init());
+  dm.Terminate();
+}
+
+// Test enumerating capabilities.
+TEST(DummyDeviceManagerTest, GetCapabilities) {
+  DummyDeviceManager dm;
+  int capabilities = dm.GetCapabilities();
+  EXPECT_EQ((cricket::AUDIO_SEND | cricket::AUDIO_RECV |
+      cricket::VIDEO_SEND | cricket::VIDEO_RECV), capabilities);
+}
+
+// Test enumerating devices.
+TEST(DummyDeviceManagerTest, GetDevices) {
+  DummyDeviceManager dm;
+  EXPECT_TRUE(dm.Init());
+  std::vector<Device> audio_ins, audio_outs, video_ins;
+  EXPECT_TRUE(dm.GetAudioInputDevices(&audio_ins));
+  EXPECT_TRUE(dm.GetAudioOutputDevices(&audio_outs));
+  EXPECT_TRUE(dm.GetVideoCaptureDevices(&video_ins));
+}
+
+// Test that we return correct ids for default and bogus devices.
+TEST(DummyDeviceManagerTest, GetAudioDeviceIds) {
+  DummyDeviceManager dm;
+  Device device;
+  EXPECT_TRUE(dm.Init());
+  EXPECT_TRUE(dm.GetAudioInputDevice(
+      cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+  EXPECT_EQ("-1", device.id);
+  EXPECT_TRUE(dm.GetAudioOutputDevice(
+      cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+  EXPECT_EQ("-1", device.id);
+  EXPECT_FALSE(dm.GetAudioInputDevice("_NOT A REAL DEVICE_", &device));
+  EXPECT_FALSE(dm.GetAudioOutputDevice("_NOT A REAL DEVICE_", &device));
+}
+
+// Test that we get the video capture device by name properly.
+TEST(DummyDeviceManagerTest, GetVideoDeviceIds) {
+  DummyDeviceManager dm;
+  Device device;
+  EXPECT_TRUE(dm.Init());
+  EXPECT_FALSE(dm.GetVideoCaptureDevice("_NOT A REAL DEVICE_", &device));
+  EXPECT_TRUE(dm.GetVideoCaptureDevice(
+      cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+}
+
+TEST(DummyDeviceManagerTest, VerifyDevicesListsAreCleared) {
+  const std::string imaginary("_NOT A REAL DEVICE_");
+  DummyDeviceManager dm;
+  std::vector<Device> audio_ins, audio_outs, video_ins;
+  audio_ins.push_back(Device(imaginary, imaginary));
+  audio_outs.push_back(Device(imaginary, imaginary));
+  video_ins.push_back(Device(imaginary, imaginary));
+  EXPECT_TRUE(dm.Init());
+  EXPECT_TRUE(dm.GetAudioInputDevices(&audio_ins));
+  EXPECT_TRUE(dm.GetAudioOutputDevices(&audio_outs));
+  EXPECT_TRUE(dm.GetVideoCaptureDevices(&video_ins));
+  for (size_t i = 0; i < audio_ins.size(); ++i) {
+    EXPECT_NE(imaginary, audio_ins[i].name);
+  }
+  for (size_t i = 0; i < audio_outs.size(); ++i) {
+    EXPECT_NE(imaginary, audio_outs[i].name);
+  }
+  for (size_t i = 0; i < video_ins.size(); ++i) {
+    EXPECT_NE(imaginary, video_ins[i].name);
+  }
+}
diff --git a/talk/session/phone/fakemediaengine.h b/talk/session/phone/fakemediaengine.h
new file mode 100644
index 0000000..b772c9c
--- /dev/null
+++ b/talk/session/phone/fakemediaengine.h
@@ -0,0 +1,831 @@
+/*
+ * 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_SESSION_PHONE_FAKEMEDIAENGINE_H_
+#define TALK_SESSION_PHONE_FAKEMEDIAENGINE_H_
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/streamparams.h"
+#include "talk/session/phone/rtputils.h"
+
+namespace cricket {
+
+class FakeMediaEngine;
+class FakeVideoEngine;
+class FakeVoiceEngine;
+
+// A common helper class that handles sending and receiving RTP/RTCP packets.
+template<class Base>
+class RtpHelper : public Base {
+ public:
+  RtpHelper()
+      : options_(0),
+        sending_(false),
+        playout_(false),
+        fail_set_send_codecs_(false),
+        fail_set_recv_codecs_(false),
+        send_ssrc_(0) {
+  }
+  const std::vector<RtpHeaderExtension>& recv_extensions() {
+    return recv_extensions_;
+  }
+  const std::vector<RtpHeaderExtension>& send_extensions() {
+    return send_extensions_;
+  }
+  bool sending() const { return sending_; }
+  bool playout() const { return playout_; }
+  const std::list<std::string>& rtp_packets() const { return rtp_packets_; }
+  const std::list<std::string>& rtcp_packets() const { return rtcp_packets_; }
+  int options() const { return options_; }
+
+  bool SendRtp(const void* data, int len) {
+    if (!sending_ || !Base::network_interface_) {
+      return false;
+    }
+    talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+    return Base::network_interface_->SendPacket(&packet);
+  }
+  bool SendRtcp(const void* data, int len) {
+    if (!Base::network_interface_) {
+      return false;
+    }
+    talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+    return Base::network_interface_->SendRtcp(&packet);
+  }
+
+  bool CheckRtp(const void* data, int len) {
+    bool success = !rtp_packets_.empty();
+    if (success) {
+      std::string packet = rtp_packets_.front();
+      rtp_packets_.pop_front();
+      success = (packet == std::string(static_cast<const char*>(data), len));
+    }
+    return success;
+  }
+  bool CheckRtcp(const void* data, int len) {
+    bool success = !rtcp_packets_.empty();
+    if (success) {
+      std::string packet = rtcp_packets_.front();
+      rtcp_packets_.pop_front();
+      success = (packet == std::string(static_cast<const char*>(data), len));
+    }
+    return success;
+  }
+  bool CheckNoRtp() {
+    return rtp_packets_.empty();
+  }
+  bool CheckNoRtcp() {
+    return rtcp_packets_.empty();
+  }
+  virtual bool SetOptions(int options) {
+    options_ = options;
+    return true;
+  }
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) {
+    recv_extensions_ = extensions;
+    return true;
+  }
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) {
+    send_extensions_ = extensions;
+    return true;
+  }
+  void set_fail_set_send_codecs(bool fail) {
+    fail_set_send_codecs_ = fail;
+  }
+  void set_fail_set_recv_codecs(bool fail) {
+    fail_set_recv_codecs_ = fail;
+  }
+  virtual bool AddSendStream(const StreamParams& sp) {
+    if (std::find(send_streams_.begin(), send_streams_.end(), sp) !=
+        send_streams_.end()) {
+        return false;
+    }
+    send_streams_.push_back(sp);
+    return true;
+  }
+  virtual bool RemoveSendStream(uint32 ssrc) {
+    return RemoveStreamBySsrc(&send_streams_, ssrc);
+  }
+  virtual bool AddRecvStream(const StreamParams& sp) {
+    if (std::find(receive_streams_.begin(), receive_streams_.end(), sp) !=
+        receive_streams_.end()) {
+        return false;
+    }
+    receive_streams_.push_back(sp);
+    return true;
+  }
+  virtual bool RemoveRecvStream(uint32 ssrc) {
+    return RemoveStreamBySsrc(&receive_streams_, ssrc);
+  }
+  const std::vector<StreamParams>& send_streams() const {
+    return send_streams_;
+  }
+  const std::vector<StreamParams>& recv_streams() const {
+    return receive_streams_;
+  }
+  bool HasRecvStream(uint32 ssrc) const {
+    return GetStreamBySsrc(receive_streams_, ssrc, NULL);
+  }
+
+  // TODO: This is to support legacy unit test that only check one
+  // sending stream.
+  const uint32 send_ssrc() {
+    if (send_streams_.empty())
+      return 0;
+    return send_streams_[0].first_ssrc();
+  }
+
+  // TODO: This is to support legacy unit test that only check one
+  // sending stream.
+  const std::string rtcp_cname() {
+    if (send_streams_.empty())
+      return "";
+    return send_streams_[0].cname;
+  }
+
+ protected:
+  bool set_sending(bool send) {
+    sending_ = send;
+    return true;
+  }
+  void set_playout(bool playout) { playout_ = playout; }
+  virtual void OnPacketReceived(talk_base::Buffer* packet) {
+    rtp_packets_.push_back(std::string(packet->data(), packet->length()));
+  }
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) {
+    rtcp_packets_.push_back(std::string(packet->data(), packet->length()));
+  }
+  bool fail_set_send_codecs() const {
+    return fail_set_send_codecs_;
+  }
+  bool fail_set_recv_codecs() const {
+    return fail_set_recv_codecs_;
+  }
+
+ private:
+  int options_;
+  bool sending_;
+  bool playout_;
+  std::vector<RtpHeaderExtension> recv_extensions_;
+  std::vector<RtpHeaderExtension> send_extensions_;
+  std::list<std::string> rtp_packets_;
+  std::list<std::string> rtcp_packets_;
+  std::vector<StreamParams> send_streams_;
+  std::vector<StreamParams> receive_streams_;
+  bool fail_set_send_codecs_;
+  bool fail_set_recv_codecs_;
+  uint32 send_ssrc_;
+  std::string rtcp_cname_;
+};
+
+class FakeVoiceMediaChannel : public RtpHelper<VoiceMediaChannel> {
+ public:
+  typedef std::pair<int, bool> DtmfEvent;
+  explicit FakeVoiceMediaChannel(FakeVoiceEngine* engine)
+      : engine_(engine),
+        muted_(false),
+        fail_set_send_(false),
+        ringback_tone_ssrc_(0),
+        ringback_tone_play_(false),
+        ringback_tone_loop_(false) {
+    output_scalings_[0] = OutputScaling();  // For default channel.
+  }
+  ~FakeVoiceMediaChannel();
+  const std::vector<AudioCodec>& recv_codecs() const { return recv_codecs_; }
+  const std::vector<AudioCodec>& send_codecs() const { return send_codecs_; }
+  const std::vector<AudioCodec>& codecs() const { return send_codecs(); }
+  bool muted() const { return muted_; }
+  const std::vector<DtmfEvent>& dtmf_queue() const { return dtmf_queue_; }
+
+  uint32 ringback_tone_ssrc() const { return ringback_tone_ssrc_; }
+  bool ringback_tone_play() const { return ringback_tone_play_; }
+  bool ringback_tone_loop() const { return ringback_tone_loop_; }
+
+  virtual bool SetRecvCodecs(const std::vector<AudioCodec> &codecs) {
+    if (fail_set_recv_codecs()) {
+      // Fake the failure in SetRecvCodecs.
+      return false;
+    }
+    recv_codecs_= codecs;
+    return true;
+  }
+  virtual bool SetSendCodecs(const std::vector<AudioCodec> &codecs) {
+    if (fail_set_send_codecs()) {
+      // Fake the failure in SetSendCodecs.
+      return false;
+    }
+    send_codecs_= codecs;
+    return true;
+  }
+  virtual bool SetPlayout(bool playout) {
+    set_playout(playout);
+    return true;
+  }
+  virtual bool SetSend(SendFlags flag) {
+    if (fail_set_send_) {
+      return false;
+    }
+    return set_sending(flag != SEND_NOTHING);
+  }
+  virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+  virtual bool Mute(bool on) {
+    muted_ = on;
+    return true;
+  }
+  virtual bool AddRecvStream(const StreamParams& sp) {
+    if (!RtpHelper<VoiceMediaChannel>::AddRecvStream(sp))
+      return false;
+    output_scalings_[sp.first_ssrc()] = OutputScaling();
+    return true;
+  }
+  virtual bool RemoveRecvStream(uint32 ssrc) {
+    if (!RtpHelper<VoiceMediaChannel>::RemoveRecvStream(ssrc))
+      return false;
+    output_scalings_.erase(ssrc);
+    return true;
+  }
+
+  virtual bool GetActiveStreams(AudioInfo::StreamList* streams) {
+    return true;
+  }
+  virtual int GetOutputLevel() { return 0; }
+
+  virtual bool SetRingbackTone(const char *buf, int len) { return true; }
+  virtual bool PlayRingbackTone(uint32 ssrc, bool play, bool loop) {
+    ringback_tone_ssrc_ = ssrc;
+    ringback_tone_play_ = play;
+    ringback_tone_loop_ = loop;
+    return true;
+  }
+
+  virtual bool PressDTMF(int event, bool playout) {
+    dtmf_queue_.push_back(std::make_pair(event, playout));
+    return true;
+  }
+
+  virtual bool SetOutputScaling(uint32 ssrc, double left, double right) {
+    if (0 == ssrc) {
+      std::map<uint32, OutputScaling>::iterator it;
+      for (it = output_scalings_.begin(); it != output_scalings_.end(); ++it) {
+        it->second.left = left;
+        it->second.right = right;
+      }
+      return true;
+    } else if (output_scalings_.find(ssrc) != output_scalings_.end()) {
+      output_scalings_[ssrc].left = left;
+      output_scalings_[ssrc].right = right;
+      return true;
+    }
+    return false;
+  }
+  virtual bool GetOutputScaling(uint32 ssrc, double* left, double* right) {
+    if (output_scalings_.find(ssrc) == output_scalings_.end()) return false;
+    *left = output_scalings_[ssrc].left;
+    *right = output_scalings_[ssrc].right;
+    return true;
+  }
+
+  virtual bool GetStats(VoiceMediaInfo* info) { return false; }
+  virtual void GetLastMediaError(uint32* ssrc,
+                                 VoiceMediaChannel::Error* error) {
+    *ssrc = 0;
+    *error = fail_set_send_ ? VoiceMediaChannel::ERROR_REC_DEVICE_OPEN_FAILED
+        : VoiceMediaChannel::ERROR_NONE;
+  }
+
+  void set_fail_set_send(bool fail) { fail_set_send_ = fail; }
+  void TriggerError(uint32 ssrc, VoiceMediaChannel::Error error) {
+    VoiceMediaChannel::SignalMediaError(ssrc, error);
+  }
+
+ private:
+  struct OutputScaling {
+    OutputScaling() : left(1.0), right(1.0) {}
+    double left, right;
+  };
+
+  FakeVoiceEngine* engine_;
+  std::vector<AudioCodec> recv_codecs_;
+  std::vector<AudioCodec> send_codecs_;
+  bool muted_;
+  std::map<uint32, OutputScaling> output_scalings_;
+  std::vector<DtmfEvent> dtmf_queue_;
+  bool fail_set_send_;
+  uint32 ringback_tone_ssrc_;
+  bool ringback_tone_play_;
+  bool ringback_tone_loop_;
+};
+
+class FakeVideoMediaChannel : public RtpHelper<VideoMediaChannel> {
+ public:
+  explicit FakeVideoMediaChannel(FakeVideoEngine* engine)
+      : engine_(engine),
+        muted_(false),
+        screen_casting_(false),
+        sent_intra_frame_(false),
+        requested_intra_frame_(false) {
+  }
+  ~FakeVideoMediaChannel();
+
+  const std::vector<VideoCodec>& recv_codecs() const { return recv_codecs_; }
+  const std::vector<VideoCodec>& send_codecs() const { return send_codecs_; }
+  const std::vector<VideoCodec>& codecs() const { return send_codecs(); }
+  bool muted() const { return muted_; }
+  bool rendering() const { return playout(); }
+  const std::map<uint32, VideoRenderer*>& renderers() const {
+    return renderers_;
+  }
+  bool GetSendStreamFormat(uint32 ssrc, VideoFormat* format) {
+    if (send_formats_.find(ssrc) == send_formats_.end()) {
+      return false;
+    }
+    *format = send_formats_[ssrc];
+    return true;
+  }
+  virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format) {
+    if (send_formats_.find(ssrc) == send_formats_.end()) {
+      return false;
+    }
+    send_formats_[ssrc] = format;
+    return true;
+  }
+
+  virtual bool AddSendStream(const StreamParams& sp) {
+    if (!RtpHelper<VideoMediaChannel>::AddSendStream(sp)) {
+      return false;
+    }
+    SetSendStreamDefaultFormat(sp.first_ssrc());
+    return true;
+  }
+  virtual bool RemoveSendStream(uint32 ssrc) {
+    send_formats_.erase(ssrc);
+    return RtpHelper<VideoMediaChannel>::RemoveSendStream(ssrc);
+  }
+
+  virtual bool SetRecvCodecs(const std::vector<VideoCodec>& codecs) {
+    if (fail_set_recv_codecs()) {
+      // Fake the failure in SetRecvCodecs.
+      return false;
+    }
+    recv_codecs_= codecs;
+    return true;
+  }
+  virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs) {
+    if (fail_set_send_codecs()) {
+      // Fake the failure in SetSendCodecs.
+      return false;
+    }
+    send_codecs_= codecs;
+
+    for (std::vector<StreamParams>::const_iterator it = send_streams().begin();
+        it != send_streams().end(); ++it) {
+      SetSendStreamDefaultFormat(it->first_ssrc());
+    }
+    return true;
+  }
+  virtual bool SetRender(bool render) {
+    set_playout(render);
+    return true;
+  }
+  virtual bool SetRenderer(uint32 ssrc, VideoRenderer* r) {
+    if (ssrc != 0 && renderers_.find(ssrc) == renderers_.end()) {
+      return false;
+    }
+    if (ssrc != 0) {
+      renderers_[ssrc] = r;
+    }
+    return true;
+  }
+
+  virtual bool SetSend(bool send) {
+    return set_sending(send);
+  }
+  virtual bool AddScreencast(uint32 ssrc, const ScreencastId& id) {
+    screen_casting_ = true;
+    return true;
+  }
+  virtual bool RemoveScreencast(uint32 ssrc) {
+    screen_casting_ = false;
+    return true;
+  }
+  virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+  virtual bool Mute(bool on) {
+    muted_ = on;
+    return true;
+  }
+  virtual bool AddRecvStream(const StreamParams& sp) {
+    if (!RtpHelper<VideoMediaChannel>::AddRecvStream(sp))
+      return false;
+    renderers_[sp.first_ssrc()] = NULL;
+    return true;
+  }
+  virtual bool RemoveRecvStream(uint32 ssrc) {
+    if (!RtpHelper<VideoMediaChannel>::RemoveRecvStream(ssrc))
+      return false;
+    renderers_.erase(ssrc);
+    return true;
+  }
+
+  virtual bool GetStats(VideoMediaInfo* info) { return false; }
+  virtual bool SendIntraFrame() {
+    sent_intra_frame_= true;
+    return true;
+  }
+  virtual bool RequestIntraFrame() {
+    requested_intra_frame_ = true;
+    return true;
+  }
+  void set_sent_intra_frame(bool v) { sent_intra_frame_ = v; }
+  bool sent_intra_frame() const { return sent_intra_frame_; }
+  void set_requested_intra_frame(bool v) { requested_intra_frame_ = v; }
+  bool requested_intra_frame() const { return requested_intra_frame_; }
+  bool screen_casting() const { return screen_casting_; }
+
+ private:
+  // Be default, each send stream uses the first send codec format.
+  void SetSendStreamDefaultFormat(uint32 ssrc) {
+    if (!send_codecs_.empty()) {
+      send_formats_[ssrc] = VideoFormat(
+           send_codecs_[0].width,
+           send_codecs_[0].height,
+           cricket::VideoFormat::FpsToInterval(send_codecs_[0].framerate),
+           cricket::FOURCC_I420);
+    }
+  }
+
+  FakeVideoEngine* engine_;
+  std::vector<VideoCodec> recv_codecs_;
+  std::vector<VideoCodec> send_codecs_;
+  std::map<uint32, VideoRenderer*> renderers_;
+  std::map<uint32, VideoFormat> send_formats_;
+  bool muted_;
+  bool screen_casting_;
+  bool sent_intra_frame_;
+  bool requested_intra_frame_;
+};
+
+class FakeSoundclipMedia : public SoundclipMedia {
+ public:
+  virtual bool PlaySound(const char *buf, int len, int flags) {
+    return true;
+  }
+};
+
+// A base class for all of the shared parts between FakeVoiceEngine
+// and FakeVideoEngine.
+class FakeBaseEngine {
+ public:
+  FakeBaseEngine()
+      : loglevel_(-1),
+        options_(0),
+        options_changed_(false),
+        fail_create_channel_(false) {
+  }
+
+  bool Init() { return true; }
+  void Terminate() {}
+
+  bool SetOptions(int options) {
+    options_ = options;
+    options_changed_ = true;
+    return true;
+  }
+
+  void SetLogging(int level, const char* filter) {
+    loglevel_ = level;
+    logfilter_ = filter;
+  }
+
+  void set_fail_create_channel(bool fail) { fail_create_channel_ = fail; }
+
+ protected:
+  int loglevel_;
+  std::string logfilter_;
+  int options_;
+  // Flag used by optionsmessagehandler_unittest for checking whether any
+  // relevant setting has been updated.
+  // TODO: Replace with explicit checks of before & after values.
+  bool options_changed_;
+  bool fail_create_channel_;
+};
+
+class FakeVoiceEngine : public FakeBaseEngine {
+ public:
+  FakeVoiceEngine()
+      : output_volume_(-1),
+        rx_processor_(NULL),
+        tx_processor_(NULL) {
+  }
+
+  int GetCapabilities() {
+    return AUDIO_SEND | AUDIO_RECV;
+  }
+
+  VoiceMediaChannel* CreateChannel() {
+    if (fail_create_channel_) {
+      return NULL;
+    }
+
+    FakeVoiceMediaChannel* ch = new FakeVoiceMediaChannel(this);
+    channels_.push_back(ch);
+    return ch;
+  }
+  FakeVoiceMediaChannel* GetChannel(size_t index) {
+    return (channels_.size() > index) ? channels_[index] : NULL;
+  }
+  void UnregisterChannel(VoiceMediaChannel* channel) {
+    channels_.erase(std::find(channels_.begin(), channels_.end(), channel));
+  }
+  SoundclipMedia* CreateSoundclip() {
+    return new FakeSoundclipMedia();
+  }
+
+  const std::vector<AudioCodec>& codecs() {
+    return codecs_;
+  }
+  void SetCodecs(const std::vector<AudioCodec> codecs) {
+    codecs_ = codecs;
+  }
+
+  bool SetDevices(const Device* in_device,
+                          const Device* out_device) {
+    in_device_ = (in_device) ? in_device->name : "";
+    out_device_ = (out_device) ? out_device->name : "";
+    options_changed_ = true;
+    return true;
+  }
+
+  bool GetOutputVolume(int* level) {
+    *level = output_volume_;
+    return true;
+  }
+
+  bool SetOutputVolume(int level) {
+    output_volume_ = level;
+    options_changed_ = true;
+    return true;
+  }
+
+  int GetInputLevel() {
+    return 0;
+  }
+
+  bool SetLocalMonitor(bool enable) {
+    return true;
+  }
+
+  bool RegisterProcessor(uint32 ssrc,
+                         VoiceProcessor* voice_processor,
+                         MediaProcessorDirection direction) {
+    if (direction == MPD_RX) {
+      rx_processor_ = voice_processor;
+      return true;
+    } else if (direction == MPD_TX) {
+      tx_processor_ = voice_processor;
+      return true;
+    }
+    return false;
+  }
+
+  bool UnregisterProcessor(uint32 ssrc,
+                           VoiceProcessor* voice_processor,
+                           MediaProcessorDirection direction) {
+    bool unregistered = false;
+    if (direction & MPD_RX) {
+      rx_processor_ = NULL;
+      unregistered = true;
+    }
+    if (direction & MPD_TX) {
+      tx_processor_ = NULL;
+      unregistered = true;
+    }
+    return unregistered;
+  }
+
+ private:
+  std::vector<FakeVoiceMediaChannel*> channels_;
+  std::vector<AudioCodec> codecs_;
+  int output_volume_;
+  std::string in_device_;
+  std::string out_device_;
+  VoiceProcessor* rx_processor_;
+  VoiceProcessor* tx_processor_;
+
+  friend class FakeMediaEngine;
+};
+
+class FakeVideoEngine : public FakeBaseEngine {
+ public:
+  FakeVideoEngine()
+      : renderer_(NULL),
+        capture_(false),
+        processor_(NULL) {
+  }
+
+  int GetCapabilities() {
+    return VIDEO_SEND | VIDEO_RECV;
+  }
+  bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) {
+    default_encoder_config_ = config;
+    return true;
+  }
+  const VideoEncoderConfig& default_encoder_config() const {
+    return default_encoder_config_;
+  }
+
+  VideoMediaChannel* CreateChannel(VoiceMediaChannel* channel) {
+    if (fail_create_channel_) {
+      return NULL;
+    }
+
+    FakeVideoMediaChannel* ch = new FakeVideoMediaChannel(this);
+    channels_.push_back(ch);
+    return ch;
+  }
+  FakeVideoMediaChannel* GetChannel(size_t index) {
+    return (channels_.size() > index) ? channels_[index] : NULL;
+  }
+  void UnregisterChannel(VideoMediaChannel* channel) {
+    channels_.erase(std::find(channels_.begin(), channels_.end(), channel));
+  }
+
+  const std::vector<VideoCodec>& codecs() const {
+    return codecs_;
+  }
+  bool FindCodec(const VideoCodec& in) {
+    for (size_t i = 0; i < codecs_.size(); ++i) {
+      if (codecs_[i].Matches(in)) {
+        return true;
+      }
+    }
+    return false;
+  }
+  void SetCodecs(const std::vector<VideoCodec> codecs) {
+    codecs_ = codecs;
+  }
+
+  bool SetCaptureDevice(const Device* device) {
+    in_device_ = (device) ? device->name : "";
+    options_changed_ = true;
+    return true;
+  }
+  bool SetLocalRenderer(VideoRenderer* r) {
+    renderer_ = r;
+    return true;
+  }
+  bool SetVideoCapturer(VideoCapturer* /*capturer*/, uint32 /*ssrc*/) {
+    return false;
+  }
+  CaptureResult SetCapture(bool capture) {
+    capture_ = capture;
+    return CR_SUCCESS;
+  }
+  bool RegisterProcessor(VideoProcessor* video_processor) {
+    processor_ = video_processor;
+    return true;
+  }
+
+  bool UnregisterProcessor(VideoProcessor* video_processor) {
+    processor_ = NULL;
+    return true;
+  }
+
+  sigslot::signal2<VideoCapturer*, CaptureResult> SignalCaptureResult;
+
+ private:
+  std::vector<FakeVideoMediaChannel*> channels_;
+  std::vector<VideoCodec> codecs_;
+  VideoEncoderConfig default_encoder_config_;
+  std::string in_device_;
+  VideoRenderer* renderer_;
+  bool capture_;
+  VideoProcessor* processor_;
+
+  friend class FakeMediaEngine;
+};
+
+class FakeMediaEngine
+    : public CompositeMediaEngine<FakeVoiceEngine, FakeVideoEngine> {
+ public:
+  FakeMediaEngine() {
+    voice_ = FakeVoiceEngine();
+    video_ = FakeVideoEngine();
+  }
+  virtual ~FakeMediaEngine() {}
+
+  virtual void SetAudioCodecs(const std::vector<AudioCodec> codecs) {
+    voice_.SetCodecs(codecs);
+  }
+
+  virtual void SetVideoCodecs(const std::vector<VideoCodec> codecs) {
+    video_.SetCodecs(codecs);
+  }
+
+  FakeVoiceMediaChannel* GetVoiceChannel(size_t index) {
+    return voice_.GetChannel(index);
+  }
+
+  FakeVideoMediaChannel* GetVideoChannel(size_t index) {
+    return video_.GetChannel(index);
+  }
+
+  int audio_options() const { return voice_.options_; }
+  int output_volume() const { return voice_.output_volume_; }
+  const VideoEncoderConfig& default_video_encoder_config() const {
+    return video_.default_encoder_config_;
+  }
+  const std::string& audio_in_device() const { return voice_.in_device_; }
+  const std::string& audio_out_device() const { return voice_.out_device_; }
+  const std::string& video_in_device() const { return video_.in_device_; }
+  VideoRenderer* local_renderer() { return video_.renderer_; }
+  int voice_loglevel() const { return voice_.loglevel_; }
+  const std::string& voice_logfilter() const { return voice_.logfilter_; }
+  int video_loglevel() const { return video_.loglevel_; }
+  const std::string& video_logfilter() const { return video_.logfilter_; }
+  bool capture() const { return video_.capture_; }
+  bool options_changed() const {
+    return voice_.options_changed_ || video_.options_changed_;
+  }
+  void clear_options_changed() {
+    video_.options_changed_ = false;
+    voice_.options_changed_ = false;
+  }
+  void set_fail_create_channel(bool fail) {
+    voice_.set_fail_create_channel(fail);
+    video_.set_fail_create_channel(fail);
+  }
+  bool video_processor_registered () const {return video_.processor_ != NULL;}
+  bool voice_processor_registered(MediaProcessorDirection direction) const {
+    if (direction == MPD_RX) {
+      return voice_.rx_processor_ != NULL;
+    } else if (direction == MPD_TX) {
+      return voice_.tx_processor_ != NULL;
+    }
+    return false;
+  }
+};
+
+// CompositeMediaEngine with FakeVoiceEngine to expose SetAudioCodecs to
+// establish a media connectionwith minimum set of audio codes required
+template<class VIDEO>
+class CompositeMediaEngineWithFakeVoiceEngine
+    : public CompositeMediaEngine<FakeVoiceEngine, VIDEO> {
+ public:
+  CompositeMediaEngineWithFakeVoiceEngine() {}
+  virtual ~CompositeMediaEngineWithFakeVoiceEngine() {}
+
+  virtual void SetAudioCodecs(const std::vector<AudioCodec>& codecs) {
+    CompositeMediaEngine<FakeVoiceEngine, VIDEO>::voice_.SetCodecs(codecs);
+  }
+};
+
+// Have to come afterwards due to declaration order
+inline FakeVoiceMediaChannel::~FakeVoiceMediaChannel() {
+  if (engine_) {
+    engine_->UnregisterChannel(this);
+  }
+}
+
+inline FakeVideoMediaChannel::~FakeVideoMediaChannel() {
+  if (engine_) {
+    engine_->UnregisterChannel(this);
+  }
+}
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FAKEMEDIAENGINE_H_
diff --git a/talk/session/phone/fakemediaprocessor.h b/talk/session/phone/fakemediaprocessor.h
new file mode 100644
index 0000000..7b88595
--- /dev/null
+++ b/talk/session/phone/fakemediaprocessor.h
@@ -0,0 +1,68 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKEMEDIAPROCESSOR_H_
+#define TALK_SESSION_PHONE_FAKEMEDIAPROCESSOR_H_
+
+#include "talk/session/phone/videoprocessor.h"
+#include "talk/session/phone/voiceprocessor.h"
+
+namespace cricket {
+
+class AudioFrame;
+
+class FakeMediaProcessor : public VoiceProcessor, public VideoProcessor {
+ public:
+  FakeMediaProcessor()
+      : voice_frame_count_(0),
+        video_frame_count_(0) {
+  }
+  virtual ~FakeMediaProcessor() {}
+
+  virtual void OnFrame(uint32 ssrc,
+                       AudioFrame* frame) {
+    ++voice_frame_count_;
+  }
+  virtual void OnFrame(uint32 ssrc,
+                       VideoFrame* frame_ptr) {
+    ++video_frame_count_;
+  }
+  virtual void OnVoiceMute(uint32 ssrc, bool muted) {}
+  virtual void OnVideoMute(uint32 ssrc, bool muted) {}
+
+  int voice_frame_count() const { return voice_frame_count_; }
+  int video_frame_count() const { return video_frame_count_; }
+
+ private:
+  // TODO: make is a map so that we can multiple ssrcs
+  int  voice_frame_count_;
+  int  video_frame_count_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FAKEMEDIAPROCESSOR_H_
diff --git a/talk/session/phone/fakenetworkinterface.h b/talk/session/phone/fakenetworkinterface.h
new file mode 100644
index 0000000..58b24a2
--- /dev/null
+++ b/talk/session/phone/fakenetworkinterface.h
@@ -0,0 +1,173 @@
+/*
+ * 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_SESSION_PHONE_FAKENETWORKINTERFACE_H_
+#define TALK_SESSION_PHONE_FAKENETWORKINTERFACE_H_
+
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/mediachannel.h"
+
+namespace cricket {
+
+// Fake NetworkInterface that sends/receives RTP/RTCP packets.
+class FakeNetworkInterface : public MediaChannel::NetworkInterface,
+                             public talk_base::MessageHandler {
+ public:
+  FakeNetworkInterface()
+      : thread_(talk_base::Thread::Current()),
+        dest_(NULL),
+        conf_(false),
+        sendbuf_size_(-1),
+        recvbuf_size_(-1) {
+  }
+
+  void SetDestination(MediaChannel* dest) { dest_ = dest; }
+
+  // Conference mode is a mode where instead of simply forwarding the packets,
+  // the transport will send multiple copies of the packet with the specified
+  // SSRCs. This allows us to simulate receiving media from multiple sources.
+  void SetConferenceMode(bool conf, const std::vector<uint32>& ssrcs) {
+    conf_ = conf;
+    ssrcs_ = ssrcs;
+  }
+
+  int NumRtpBytes() {
+    talk_base::CritScope cs(&crit_);
+    int bytes = 0;
+    for (size_t i = 0; i < rtp_packets_.size(); ++i) {
+      bytes += rtp_packets_[i].length();
+    }
+    return bytes;
+  }
+
+  int NumRtpPackets() {
+    talk_base::CritScope cs(&crit_);
+    return rtp_packets_.size();
+  }
+
+  // Note: callers are responsible for deleting the returned buffer.
+  const talk_base::Buffer* GetRtpPacket(int index) {
+    talk_base::CritScope cs(&crit_);
+    if (index >= NumRtpPackets()) {
+      return NULL;
+    }
+    return new talk_base::Buffer(rtp_packets_[index]);
+  }
+
+  int NumRtcpPackets() {
+    talk_base::CritScope cs(&crit_);
+    return rtcp_packets_.size();
+  }
+
+  // Note: callers are responsible for deleting the returned buffer.
+  const talk_base::Buffer* GetRtcpPacket(int index) {
+    talk_base::CritScope cs(&crit_);
+    if (index >= NumRtcpPackets()) {
+      return NULL;
+    }
+    return new talk_base::Buffer(rtcp_packets_[index]);
+  }
+
+  int sendbuf_size() const { return sendbuf_size_; }
+  int recvbuf_size() const { return recvbuf_size_; }
+
+ protected:
+  virtual bool SendPacket(talk_base::Buffer* packet) {
+    talk_base::CritScope cs(&crit_);
+    rtp_packets_.push_back(*packet);
+    if (conf_) {
+      talk_base::Buffer buffer_copy(*packet);
+      for (size_t i = 0; i < ssrcs_.size(); ++i) {
+        talk_base::SetBE32(buffer_copy.data() + 8, ssrcs_[i]);
+        PostMessage(ST_RTP, buffer_copy);
+      }
+    } else {
+      PostMessage(ST_RTP, *packet);
+    }
+    return true;
+  }
+
+  virtual bool SendRtcp(talk_base::Buffer* packet) {
+    talk_base::CritScope cs(&crit_);
+    rtcp_packets_.push_back(*packet);
+    if (!conf_) {
+      // don't worry about RTCP in conf mode for now
+      PostMessage(ST_RTCP, *packet);
+    }
+    return true;
+  }
+
+  virtual int SetOption(SocketType type, talk_base::Socket::Option opt,
+                        int option) {
+    if (opt == talk_base::Socket::OPT_SNDBUF) {
+      sendbuf_size_ = option;
+    } else if (opt == talk_base::Socket::OPT_RCVBUF) {
+      recvbuf_size_ = option;
+    }
+    return 0;
+  }
+
+  void PostMessage(int id, const talk_base::Buffer& packet) {
+    thread_->Post(this, id, talk_base::WrapMessageData(packet));
+  }
+
+  virtual void OnMessage(talk_base::Message* msg) {
+    talk_base::TypedMessageData<talk_base::Buffer>* msg_data =
+        static_cast<talk_base::TypedMessageData<talk_base::Buffer>*>(
+            msg->pdata);
+    if (dest_) {
+      if (msg->message_id == ST_RTP) {
+        dest_->OnPacketReceived(&msg_data->data());
+      } else {
+        dest_->OnRtcpReceived(&msg_data->data());
+      }
+    }
+    delete msg_data;
+  }
+
+ private:
+  talk_base::Thread* thread_;
+  MediaChannel* dest_;
+  bool conf_;
+  std::vector<uint32> ssrcs_;
+  talk_base::CriticalSection crit_;
+  std::vector<talk_base::Buffer> rtp_packets_;
+  std::vector<talk_base::Buffer> rtcp_packets_;
+  int sendbuf_size_;
+  int recvbuf_size_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FAKENETWORKINTERFACE_H_
diff --git a/talk/session/phone/fakertp.h b/talk/session/phone/fakertp.h
new file mode 100644
index 0000000..30e3793
--- /dev/null
+++ b/talk/session/phone/fakertp.h
@@ -0,0 +1,97 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Fake RTP and RTCP packets to use in unit tests.
+
+#ifndef TALK_SESSION_PHONE_FAKERTP_H_
+#define TALK_SESSION_PHONE_FAKERTP_H_
+
+// A typical PCMU RTP packet.
+// PT=0, SN=1, TS=0, SSRC=1
+// all data FF
+static const unsigned char kPcmuFrame[] = {
+  0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+};
+
+// A typical Receiver Report RTCP packet.
+// PT=RR, LN=1, SSRC=1
+// send SSRC=2, all other fields 0
+static const unsigned char kRtcpReport[] = {
+  0x80, 0xc9, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+  0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+// PT = 97, TS = 0, Seq = 1, SSRC = 2
+// H264 - NRI = 1, Type = 1, bit stream = FF
+
+static const unsigned char kH264Packet[] = {
+  0x80, 0x61, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+  0x21, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+};
+
+#endif  // TALK_SESSION_PHONE_FAKERTP_H_
diff --git a/talk/session/phone/fakevideocapturer.h b/talk/session/phone/fakevideocapturer.h
new file mode 100644
index 0000000..335bc8d
--- /dev/null
+++ b/talk/session/phone/fakevideocapturer.h
@@ -0,0 +1,118 @@
+/*
+ * 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_SESSION_PHONE_FAKEVIDEOCAPTURER_H_
+#define TALK_SESSION_PHONE_FAKEVIDEOCAPTURER_H_
+
+#include <vector>
+
+#include "talk/session/phone/videocapturer.h"
+#include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+
+// Fake video capturer that allows the test to manually pump in frames.
+class FakeVideoCapturer : public cricket::VideoCapturer {
+ public:
+  FakeVideoCapturer()
+      : running_(false),
+        next_timestamp_(0) {
+    // Default supported formats. Use ResetSupportedFormats to over write.
+    std::vector<cricket::VideoFormat> formats;
+    formats.push_back(cricket::VideoFormat(640, 480,
+        cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+    formats.push_back(cricket::VideoFormat(320, 240,
+        cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+    formats.push_back(cricket::VideoFormat(160, 120,
+        cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+    ResetSupportedFormats(formats);
+  }
+  ~FakeVideoCapturer() {
+    SignalDestroyed(this);
+  }
+
+  void ResetSupportedFormats(const std::vector<cricket::VideoFormat>& formats) {
+    SetSupportedFormats(formats);
+  }
+  bool CaptureFrame() {
+    if (!GetCaptureFormat()) {
+      return false;
+    }
+    return CaptureCustomFrame(GetCaptureFormat()->width,
+                              GetCaptureFormat()->height,
+                              GetCaptureFormat()->fourcc);
+  }
+  bool CaptureCustomFrame(int width, int height, uint32 fourcc) {
+    if (!running_) {
+      return false;
+    }
+
+    // Currently, |fourcc| is always I420.
+    uint32 size = cricket::VideoFrame::SizeOf(width, height);
+    cricket::CapturedFrame frame;
+    frame.width = width;
+    frame.height = height;
+    frame.fourcc = fourcc;
+    frame.data_size = size;
+    frame.elapsed_time = frame.time_stamp = next_timestamp_;
+    next_timestamp_ += 33333333;  // 30 fps
+
+    talk_base::scoped_array<char> data(new char[size]);
+    memset(data.get(), 0, size);
+    frame.data = data.get();
+    SignalFrameCaptured(this, &frame);
+    return true;
+  }
+  sigslot::signal1<FakeVideoCapturer*> SignalDestroyed;
+
+  virtual cricket::CaptureResult Start(const cricket::VideoFormat& format) {
+    cricket::VideoFormat supported;
+    if (GetBestCaptureFormat(format, &supported)) {
+      SetCaptureFormat(&supported);
+    }
+    running_ = true;
+    return cricket::CR_SUCCESS;
+  }
+  virtual void Stop() {
+    running_ = false;
+    SetCaptureFormat(NULL);
+  }
+  virtual bool IsRunning() { return running_; }
+  bool GetPreferredFourccs(std::vector<uint32>* fourccs) {
+    fourccs->push_back(cricket::FOURCC_I420);
+    return true;
+  }
+
+ private:
+  bool running_;
+  int64 next_timestamp_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FAKEVIDEOCAPTURER_H_
diff --git a/talk/session/phone/fakevideorenderer.h b/talk/session/phone/fakevideorenderer.h
new file mode 100644
index 0000000..3227fa4
--- /dev/null
+++ b/talk/session/phone/fakevideorenderer.h
@@ -0,0 +1,77 @@
+/*
+ * 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_FAKEVIDEORENDERER_H_
+#define TALK_SESSION_PHONE_FAKEVIDEORENDERER_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/videorenderer.h"
+
+namespace cricket {
+
+// Faked video renderer that has a callback for actions on rendering.
+class FakeVideoRenderer : public VideoRenderer {
+ public:
+  FakeVideoRenderer()
+      : width_(0),
+        height_(0),
+        num_set_sizes_(0),
+        num_rendered_frames_(0) {
+  }
+
+  virtual bool SetSize(int width, int height, int reserved) {
+    width_ = width;
+    height_ = height;
+    ++num_set_sizes_;
+    SignalSetSize(width, height, reserved);
+    return true;
+  }
+
+  virtual bool RenderFrame(const VideoFrame* frame) {
+    ++num_rendered_frames_;
+    SignalRenderFrame(frame);
+    return true;
+  }
+
+  int width() const { return width_; }
+  int height() const { return height_; }
+  int num_set_sizes() const { return num_set_sizes_; }
+  int num_rendered_frames() const { return num_rendered_frames_; }
+
+  sigslot::signal3<int, int, int> SignalSetSize;
+  sigslot::signal1<const VideoFrame*> SignalRenderFrame;
+
+ private:
+  int width_;
+  int height_;
+  int num_set_sizes_;
+  int num_rendered_frames_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FAKEVIDEORENDERER_H_
diff --git a/talk/session/phone/fakewebrtccommon.h b/talk/session/phone/fakewebrtccommon.h
new file mode 100644
index 0000000..a960e8b
--- /dev/null
+++ b/talk/session/phone/fakewebrtccommon.h
@@ -0,0 +1,55 @@
+/*
+ * 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_FAKEWEBRTCCOMMON_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCCOMMON_H_
+
+namespace cricket {
+
+#define WEBRTC_STUB(method, args) \
+  virtual int method args { return 0; }
+
+#define WEBRTC_STUB_CONST(method, args) \
+  virtual int method args const { return 0; }
+
+#define WEBRTC_FUNC(method, args) \
+  virtual int method args
+
+#define WEBRTC_FUNC_CONST(method, args) \
+  virtual int method args const
+
+#define WEBRTC_BOOL_FUNC(method, args) \
+  virtual bool method args
+
+#define WEBRTC_CHECK_CHANNEL(channel) \
+  if (channels_.find(channel) == channels_.end()) return -1;
+
+#define WEBRTC_ASSERT_CHANNEL(channel) \
+  ASSERT(channels_.find(channel) != channels_.end());
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FAKEWEBRTCCOMMON_H_
diff --git a/talk/session/phone/fakewebrtcdeviceinfo.h b/talk/session/phone/fakewebrtcdeviceinfo.h
new file mode 100644
index 0000000..c969236
--- /dev/null
+++ b/talk/session/phone/fakewebrtcdeviceinfo.h
@@ -0,0 +1,123 @@
+// 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_SESSION_PHONE_FAKEWEBRTCDEVICEINFO_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCDEVICEINFO_H_
+
+#include <vector>
+
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+// Fake class for mocking out webrtc::VideoCaptureModule::DeviceInfo.
+class FakeWebRtcDeviceInfo : public webrtc::VideoCaptureModule::DeviceInfo {
+ public:
+  struct Device {
+    Device(const std::string& n, const std::string& i) : name(n), id(i) {}
+    std::string name;
+    std::string id;
+    std::string product;
+    std::vector<webrtc::VideoCaptureCapability> caps;
+  };
+  FakeWebRtcDeviceInfo() {}
+  void AddDevice(const std::string& device_name, const std::string& device_id) {
+    devices_.push_back(Device(device_name, device_id));
+  }
+  void AddCapability(const std::string& device_id,
+                     const webrtc::VideoCaptureCapability& cap) {
+    Device* dev = GetDeviceById(
+        reinterpret_cast<const WebRtc_UWord8*>(device_id.c_str()));
+    if (!dev) return;
+    dev->caps.push_back(cap);
+  }
+  virtual WebRtc_UWord32 NumberOfDevices() {
+    return devices_.size();
+  }
+  virtual WebRtc_Word32 GetDeviceName(WebRtc_UWord32 device_num,
+                                      WebRtc_UWord8* device_name,
+                                      WebRtc_UWord32 device_name_len,
+                                      WebRtc_UWord8* device_id,
+                                      WebRtc_UWord32 device_id_len,
+                                      WebRtc_UWord8* product_id,
+                                      WebRtc_UWord32 product_id_len) {
+    Device* dev = GetDeviceByIndex(device_num);
+    if (!dev) return -1;
+    talk_base::strcpyn(reinterpret_cast<char*>(device_name), device_name_len,
+                       dev->name.c_str());
+    talk_base::strcpyn(reinterpret_cast<char*>(device_id), device_id_len,
+                       dev->id.c_str());
+    if (product_id) {
+      talk_base::strcpyn(reinterpret_cast<char*>(product_id), product_id_len,
+                         dev->product.c_str());
+    }
+    return 0;
+  }
+  virtual WebRtc_Word32 NumberOfCapabilities(const WebRtc_UWord8* device_id) {
+    Device* dev = GetDeviceById(device_id);
+    if (!dev) return -1;
+    return dev->caps.size();
+  }
+  virtual WebRtc_Word32 GetCapability(const WebRtc_UWord8* device_id,
+                                      const WebRtc_UWord32 device_cap_num,
+                                      webrtc::VideoCaptureCapability& cap) {
+    Device* dev = GetDeviceById(device_id);
+    if (!dev) return -1;
+    if (device_cap_num >= dev->caps.size()) return -1;
+    cap = dev->caps[device_cap_num];
+    return 0;
+  }
+  virtual WebRtc_Word32 GetOrientation(const WebRtc_UWord8* device_id,
+                                       webrtc::VideoCaptureRotation& rotation) {
+    return -1;  // not implemented
+  }
+  virtual WebRtc_Word32 GetBestMatchedCapability(
+      const WebRtc_UWord8* device_id,
+      const webrtc::VideoCaptureCapability requested,
+      webrtc::VideoCaptureCapability& resulting) {
+    return -1;  // not implemented
+  }
+  virtual WebRtc_Word32 DisplayCaptureSettingsDialogBox(
+      const WebRtc_UWord8* device_id, const WebRtc_UWord8* dialog_title,
+      void* parent, WebRtc_UWord32 x, WebRtc_UWord32 y) {
+    return -1;  // not implemented
+  }
+
+  Device* GetDeviceByIndex(size_t num) {
+    return (num < devices_.size()) ? &devices_[num] : NULL;
+  }
+  Device* GetDeviceById(const WebRtc_UWord8* device_id) {
+    for (size_t i = 0; i < devices_.size(); ++i) {
+      if (devices_[i].id == reinterpret_cast<const char*>(device_id)) {
+        return &devices_[i];
+      }
+    }
+    return NULL;
+  }
+
+ private:
+  std::vector<Device> devices_;
+};
+
+#endif  // TALK_SESSION_PHONE_FAKEWEBRTCDEVICEINFO_H_
diff --git a/talk/session/phone/fakewebrtcvcmfactory.h b/talk/session/phone/fakewebrtcvcmfactory.h
new file mode 100644
index 0000000..e5c660c
--- /dev/null
+++ b/talk/session/phone/fakewebrtcvcmfactory.h
@@ -0,0 +1,63 @@
+// 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_SESSION_PHONE_FAKEWEBRTCVCMFACTORY_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCVCMFACTORY_H_
+
+#include <vector>
+
+#include "talk/session/phone/fakewebrtcvideocapturemodule.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+// Factory class to allow the fakes above to be injected into
+// WebRtcVideoCapturer.
+class FakeWebRtcVcmFactory : public cricket::WebRtcVcmFactoryInterface {
+ public:
+  virtual webrtc::VideoCaptureModule* Create(int module_id,
+                                             const WebRtc_UWord8* device_id) {
+    if (!device_info.GetDeviceById(device_id)) return NULL;
+    FakeWebRtcVideoCaptureModule* module =
+        new FakeWebRtcVideoCaptureModule(this, module_id);
+    modules.push_back(module);
+    return module;
+  }
+  virtual webrtc::VideoCaptureModule::DeviceInfo* CreateDeviceInfo(int id) {
+    return &device_info;
+  }
+  virtual void DestroyDeviceInfo(webrtc::VideoCaptureModule::DeviceInfo* info) {
+  }
+  void OnDestroyed(webrtc::VideoCaptureModule* module) {
+    std::remove(modules.begin(), modules.end(), module);
+  }
+  FakeWebRtcDeviceInfo device_info;
+  std::vector<FakeWebRtcVideoCaptureModule*> modules;
+};
+
+FakeWebRtcVideoCaptureModule::~FakeWebRtcVideoCaptureModule() {
+  if (factory_)
+    factory_->OnDestroyed(this);
+}
+
+#endif  // TALK_SESSION_PHONE_FAKEWEBRTCVCMFACTORY_H_
diff --git a/talk/session/phone/fakewebrtcvideocapturemodule.h b/talk/session/phone/fakewebrtcvideocapturemodule.h
new file mode 100644
index 0000000..e3d7db1
--- /dev/null
+++ b/talk/session/phone/fakewebrtcvideocapturemodule.h
@@ -0,0 +1,169 @@
+// 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_SESSION_PHONE_FAKEWEBRTCVIDEOCAPTUREMODULE_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCVIDEOCAPTUREMODULE_H_
+
+#include <vector>
+
+#include "talk/session/phone/testutils.h"
+#include "talk/session/phone/fakewebrtcdeviceinfo.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+class FakeWebRtcVcmFactory;
+
+// Fake class for mocking out webrtc::VideoCaptureModule.
+class FakeWebRtcVideoCaptureModule : public webrtc::VideoCaptureModule {
+ public:
+  FakeWebRtcVideoCaptureModule(FakeWebRtcVcmFactory* factory, WebRtc_Word32 id)
+      : factory_(factory),
+        id_(id),
+        callback_(NULL),
+        running_(false),
+        delay_(0) {
+  }
+  virtual int32_t Version(char* version,
+                          uint32_t& remaining_buffer_in_bytes,
+                          uint32_t& position) const {
+    return 0;
+  }
+  virtual int32_t TimeUntilNextProcess() {
+    return 0;
+  }
+  virtual int32_t Process() {
+    return 0;
+  }
+  virtual WebRtc_Word32 ChangeUniqueId(const WebRtc_Word32 id) {
+    id_ = id;
+    return 0;
+  }
+  virtual WebRtc_Word32 RegisterCaptureDataCallback(
+      webrtc::VideoCaptureDataCallback& callback) {
+    callback_ = &callback;
+    return 0;
+  }
+  virtual WebRtc_Word32 DeRegisterCaptureDataCallback() {
+    callback_ = NULL;
+    return 0;
+  }
+  virtual WebRtc_Word32 RegisterCaptureCallback(
+      webrtc::VideoCaptureFeedBack& callback) {
+    return -1;  // not implemented
+  }
+  virtual WebRtc_Word32 DeRegisterCaptureCallback() {
+    return 0;
+  }
+  virtual WebRtc_Word32 StartCapture(
+      const webrtc::VideoCaptureCapability& cap) {
+    if (running_) return -1;
+    cap_ = cap;
+    running_ = true;
+    return 0;
+  }
+  virtual WebRtc_Word32 StopCapture() {
+    running_ = false;
+    return 0;
+  }
+  virtual WebRtc_Word32 StartSendImage(const webrtc::VideoFrame& frame,
+                                       WebRtc_Word32 framerate) {
+    return -1;  // not implemented
+  }
+  virtual WebRtc_Word32 StopSendImage() {
+    return 0;
+  }
+  virtual const WebRtc_UWord8* CurrentDeviceName() const {
+    return NULL;  // not implemented
+  }
+  virtual bool CaptureStarted() {
+    return running_;
+  }
+  virtual WebRtc_Word32 CaptureSettings(
+      webrtc::VideoCaptureCapability& settings) {
+    if (!running_) return -1;
+    settings = cap_;
+    return 0;
+  }
+  virtual WebRtc_Word32 SetCaptureDelay(WebRtc_Word32 delay) {
+    delay_ = delay;
+    return 0;
+  }
+  virtual WebRtc_Word32 CaptureDelay() {
+    return delay_;
+  }
+  virtual WebRtc_Word32 SetCaptureRotation(
+      webrtc::VideoCaptureRotation rotation) {
+    return -1;  // not implemented
+  }
+  virtual VideoCaptureEncodeInterface* GetEncodeInterface(
+      const webrtc::VideoCodec& codec) {
+    return NULL;  // not implemented
+  }
+  virtual WebRtc_Word32 EnableFrameRateCallback(const bool enable) {
+    return -1;  // not implemented
+  }
+  virtual WebRtc_Word32 EnableNoPictureAlarm(const bool enable) {
+    return -1;  // not implemented
+  }
+  virtual int32_t AddRef() {
+    return 0;
+  }
+  virtual int32_t Release() {
+    delete this;
+    return 0;
+  }
+
+  bool SendFrame(int w, int h) {
+    if (!running_) return false;
+    webrtc::VideoFrame sample;
+    sample.SetWidth(w);
+    sample.SetHeight(h);
+    if (sample.VerifyAndAllocate(I420_SIZE(w, h)) == -1 ||
+        sample.SetLength(sample.Size()) == -1) {
+      return false;
+    }
+    if (callback_) {
+      callback_->OnIncomingCapturedFrame(id_, sample,
+                                         webrtc::kVideoCodecUnknown);
+    }
+    return true;
+  }
+
+  const webrtc::VideoCaptureCapability& cap() const {
+    return cap_;
+  }
+
+ private:
+  // Ref-counted, use Release() instead.
+  ~FakeWebRtcVideoCaptureModule();
+
+  FakeWebRtcVcmFactory* factory_;
+  int id_;
+  webrtc::VideoCaptureDataCallback* callback_;
+  bool running_;
+  webrtc::VideoCaptureCapability cap_;
+  int delay_;
+};
+
+#endif  // TALK_SESSION_PHONE_FAKEWEBRTCVIDEOCAPTUREMODULE_H_
diff --git a/talk/session/phone/fakewebrtcvideoengine.h b/talk/session/phone/fakewebrtcvideoengine.h
new file mode 100644
index 0000000..cbac785
--- /dev/null
+++ b/talk/session/phone/fakewebrtcvideoengine.h
@@ -0,0 +1,628 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKEWEBRTCVIDEOENGINE_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCVIDEOENGINE_H_
+
+#include <list>
+#include <map>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/fakewebrtccommon.h"
+#include "talk/session/phone/webrtcvie.h"
+
+namespace webrtc {
+
+bool operator==(const webrtc::VideoCodec& c1, const webrtc::VideoCodec& c2) {
+  return memcmp(&c1, &c2, sizeof(c1)) == 0;
+}
+
+}
+
+namespace cricket {
+
+#define WEBRTC_CHECK_CAPTURER(capturer) \
+  if (capturers_.find(capturer) == capturers_.end()) return -1;
+
+#define WEBRTC_ASSERT_CAPTURER(capturer) \
+  ASSERT(capturers_.find(capturer) != capturers_.end());
+
+static const int kStartVideoBitrate = 300;
+static const int kMaxVideoBitrate = 1000;
+
+// WebRtc channel id and capture id share the same number space.
+// This is how AddRenderer(renderId, ...) is able to tell if it is adding a
+// renderer for a channel or it is adding a renderer for a capturer.
+static const int kViEChannelIdBase = 0;
+static const int kViEChannelIdMax = 1000;
+static const int kViECaptureIdBase = 10000;  // Make sure there is a gap.
+static const int kViECaptureIdMax = 11000;
+
+class FakeWebRtcVideoEngine
+    : public webrtc::ViEBase,
+      public webrtc::ViECodec,
+      public webrtc::ViECapture,
+      public webrtc::ViENetwork,
+      public webrtc::ViERender,
+      public webrtc::ViERTP_RTCP,
+      public webrtc::ViEImageProcess {
+ public:
+  struct Channel {
+    Channel()
+        : capture_id_(-1),
+          has_renderer_(false),
+          render_started_(false),
+          send(false),
+          local_ssrc_(0),
+          rtcp_status_(webrtc::kRtcpNone),
+          key_frame_request_method_(webrtc::kViEKeyFrameRequestNone),
+          tmmbr_(false),
+          remb_send_(false),
+          remb_(false),
+          nack_(false),
+          hybrid_nack_fec_(false) {
+      memset(&send_codec, 0, sizeof(send_codec));
+    }
+    int capture_id_;
+    bool has_renderer_;
+    bool render_started_;
+    bool send;
+    int local_ssrc_;
+    std::string cname_;
+    webrtc::ViERTCPMode rtcp_status_;
+    webrtc::ViEKeyFrameRequestMethod key_frame_request_method_;
+    bool tmmbr_;
+    bool remb_send_;  // This channel send REMB packets.
+    bool remb_;  // This channel report BWE using remb.
+    bool nack_;
+    bool hybrid_nack_fec_;
+    std::vector<webrtc::VideoCodec> recv_codecs;
+    webrtc::VideoCodec send_codec;
+  };
+  class Capturer : public webrtc::ViEExternalCapture {
+   public:
+    Capturer() : channel_id_(-1) { }
+    int channel_id() const { return channel_id_; }
+    void set_channel_id(int channel_id) { channel_id_ = channel_id; }
+
+    // From ViEExternalCapture
+    virtual int IncomingFrame(unsigned char* videoFrame,
+                              unsigned int videoFrameLength,
+                              unsigned short width,
+                              unsigned short height,
+                              webrtc::RawVideoType videoType,
+                              unsigned long long captureTime) {
+      return 0;
+    }
+    virtual int IncomingFrameI420(
+        const webrtc::ViEVideoFrameI420& video_frame,
+        unsigned long long captureTime) {
+      return 0;
+    }
+
+   private:
+    int channel_id_;
+  };
+
+  FakeWebRtcVideoEngine(const cricket::VideoCodec* const* codecs,
+                        int num_codecs)
+      : inited_(false),
+        last_channel_(kViEChannelIdBase - 1),
+        fail_create_channel_(false),
+        last_capturer_(kViECaptureIdBase - 1),
+        fail_alloc_capturer_(false),
+        codecs_(codecs),
+        num_codecs_(num_codecs) {
+  }
+
+  ~FakeWebRtcVideoEngine() {
+    ASSERT(0 == channels_.size());
+    ASSERT(0 == capturers_.size());
+  }
+  bool IsInited() const { return inited_; }
+
+  int GetLastChannel() const { return last_channel_; }
+  int GetNumChannels() const { return channels_.size(); }
+  void set_fail_create_channel(bool fail_create_channel) {
+    fail_create_channel_ = fail_create_channel;
+  }
+
+  int GetLastCapturer() const { return last_capturer_; }
+  int GetNumCapturers() const { return capturers_.size(); }
+  void set_fail_alloc_capturer(bool fail_alloc_capturer) {
+    fail_alloc_capturer_ = fail_alloc_capturer;
+  }
+
+  int GetCaptureId(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->capture_id_;
+  }
+  bool GetHasRenderer(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->has_renderer_;
+  }
+  bool GetRenderStarted(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->render_started_;
+  }
+  bool GetSend(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->send;
+  }
+  int GetCaptureChannelId(int capture_id) const {
+    WEBRTC_ASSERT_CAPTURER(capture_id);
+    return capturers_.find(capture_id)->second->channel_id();
+  }
+  webrtc::ViERTCPMode GetRtcpStatus(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->rtcp_status_;
+  }
+  webrtc::ViEKeyFrameRequestMethod GetKeyFrameRequestMethod(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->key_frame_request_method_;
+  }
+  bool GetTmmbrStatus(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->tmmbr_;
+  }
+  bool GetRembStatus(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->remb_;
+  }
+  bool GetRembStatusSend(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->remb_send_;
+  }
+  bool GetNackStatus(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->nack_;
+  }
+  bool GetHybridNackFecStatus(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->hybrid_nack_fec_;
+  }
+
+  bool ReceiveCodecRegistered(int channel,
+                              const webrtc::VideoCodec& codec) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    const std::vector<webrtc::VideoCodec>& codecs =
+      channels_.find(channel)->second->recv_codecs;
+    return std::find(codecs.begin(), codecs.end(), codec) != codecs.end();
+  };
+
+  WEBRTC_STUB(Release, ());
+
+  // webrtc::ViEBase
+  WEBRTC_FUNC(Init, ()) {
+    inited_ = true;
+    return 0;
+  };
+  WEBRTC_STUB(SetVoiceEngine, (webrtc::VoiceEngine*));
+  WEBRTC_FUNC(CreateChannel, (int& channel)) {  // NOLINT
+    if (fail_create_channel_) {
+      return -1;
+    }
+    if (kViEChannelIdMax == last_channel_) {
+      return -1;
+    }
+    Channel* ch = new Channel();
+    channels_[++last_channel_] = ch;
+    channel = last_channel_;
+    return 0;
+  };
+  WEBRTC_STUB(CreateChannel, (int&, int));
+  WEBRTC_FUNC(DeleteChannel, (const int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    delete channels_[channel];
+    channels_.erase(channel);
+    return 0;
+  }
+  WEBRTC_STUB(ConnectAudioChannel, (const int, const int));
+  WEBRTC_STUB(DisconnectAudioChannel, (const int));
+  WEBRTC_FUNC(StartSend, (const int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->send = true;
+    return 0;
+  }
+  WEBRTC_FUNC(StopSend, (const int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->send = false;
+    return 0;
+  }
+  WEBRTC_STUB(StartReceive, (const int));
+  WEBRTC_STUB(StopReceive, (const int));
+  WEBRTC_STUB(RegisterObserver, (webrtc::ViEBaseObserver&));
+  WEBRTC_STUB(DeregisterObserver, ());
+  WEBRTC_STUB(GetVersion, (char version[1024]));
+  WEBRTC_STUB(LastError, ());
+
+  // webrtc::ViECodec
+  WEBRTC_FUNC(NumberOfCodecs, ()) const {
+    return num_codecs_;
+  };
+  WEBRTC_FUNC(GetCodec, (const unsigned char list_number,
+                         webrtc::VideoCodec& out_codec)) const {
+    if (list_number >= NumberOfCodecs()) {
+      return -1;
+    }
+    memset(&out_codec, 0, sizeof(out_codec));
+    const cricket::VideoCodec& c(*codecs_[list_number]);
+    if ("I420" == c.name) {
+      out_codec.codecType = webrtc::kVideoCodecI420;
+    } else if ("VP8" == c.name) {
+      out_codec.codecType = webrtc::kVideoCodecVP8;
+    } else if ("red" == c.name) {
+      out_codec.codecType = webrtc::kVideoCodecRED;
+    } else if ("ulpfec" == c.name) {
+      out_codec.codecType = webrtc::kVideoCodecULPFEC;
+    } else {
+      out_codec.codecType = webrtc::kVideoCodecUnknown;
+    }
+    talk_base::strcpyn(out_codec.plName, sizeof(out_codec.plName),
+                       c.name.c_str());
+    out_codec.plType = c.id;
+    out_codec.width = c.width;
+    out_codec.height = c.height;
+    out_codec.startBitrate = kStartVideoBitrate;
+    out_codec.maxBitrate = kMaxVideoBitrate;
+    out_codec.minBitrate = kStartVideoBitrate;
+    out_codec.maxFramerate = c.framerate;
+    return 0;
+  };
+  WEBRTC_FUNC(SetSendCodec, (const int channel,
+                             const webrtc::VideoCodec& codec)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->send_codec = codec;
+    return 0;
+  };
+  WEBRTC_FUNC(GetSendCodec, (const int channel,
+                             webrtc::VideoCodec& codec)) const {  // NOLINT
+    WEBRTC_CHECK_CHANNEL(channel);
+    codec = channels_.find(channel)->second->send_codec;
+    return 0;
+  };
+  WEBRTC_FUNC(SetReceiveCodec, (const int channel,
+                                const webrtc::VideoCodec& codec)) {  // NOLINT
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->recv_codecs.push_back(codec);
+    return 0;
+  };
+  WEBRTC_STUB_CONST(GetReceiveCodec, (const int, webrtc::VideoCodec&));
+  WEBRTC_STUB_CONST(GetCodecConfigParameters, (const int,
+      unsigned char*, unsigned char&));
+  WEBRTC_STUB(SetImageScaleStatus, (const int, const bool));
+  WEBRTC_STUB_CONST(GetSendCodecStastistics, (const int,
+      unsigned int&, unsigned int&));
+  WEBRTC_STUB_CONST(GetReceiveCodecStastistics, (const int,
+      unsigned int&, unsigned int&));
+  virtual unsigned int GetDiscardedPackets(const int channel) const {
+    return 0;
+  }
+
+  WEBRTC_STUB(SetKeyFrameRequestCallbackStatus, (const int, const bool));
+  WEBRTC_STUB(SetSignalKeyPacketLossStatus, (const int, const bool,
+      const bool));
+  WEBRTC_STUB(RegisterEncoderObserver, (const int,
+      webrtc::ViEEncoderObserver&));
+  WEBRTC_STUB(DeregisterEncoderObserver, (const int));
+  WEBRTC_STUB(RegisterDecoderObserver, (const int,
+      webrtc::ViEDecoderObserver&));
+  WEBRTC_STUB(DeregisterDecoderObserver, (const int));
+  WEBRTC_STUB(SendKeyFrame, (const int));
+  WEBRTC_STUB(WaitForFirstKeyFrame, (const int, const bool));
+  WEBRTC_STUB(SetInverseH263Logic, (int, bool));
+
+  // webrtc::ViECapture
+  WEBRTC_STUB(NumberOfCaptureDevices, ());
+  WEBRTC_STUB(GetCaptureDevice, (unsigned int, char*,
+      const unsigned int, char*, const unsigned int));
+  WEBRTC_STUB(AllocateCaptureDevice, (const char*, const unsigned int, int&));
+  WEBRTC_FUNC(AllocateExternalCaptureDevice,
+              (int& capture_id, webrtc::ViEExternalCapture*& capture)) {
+    if (fail_alloc_capturer_) {
+      return -1;
+    }
+    if (kViECaptureIdMax == last_capturer_) {
+      return -1;
+    }
+    Capturer* cap = new Capturer();
+    capturers_[++last_capturer_] = cap;
+    capture_id = last_capturer_;
+    capture = cap;
+    return 0;
+  }
+  WEBRTC_STUB(AllocateCaptureDevice, (webrtc::VideoCaptureModule&, int&));
+  WEBRTC_FUNC(ReleaseCaptureDevice, (const int capture_id)) {
+    WEBRTC_CHECK_CAPTURER(capture_id);
+    delete capturers_[capture_id];
+    capturers_.erase(capture_id);
+    return 0;
+  }
+  WEBRTC_FUNC(ConnectCaptureDevice, (const int capture_id,
+                                     const int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    WEBRTC_CHECK_CAPTURER(capture_id);
+    channels_[channel]->capture_id_ = capture_id;
+    capturers_[capture_id]->set_channel_id(channel);
+    return 0;
+  }
+  WEBRTC_FUNC(DisconnectCaptureDevice, (const int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    int capture_id = channels_[channel]->capture_id_;
+    WEBRTC_CHECK_CAPTURER(capture_id);
+    channels_[channel]->capture_id_ = -1;
+    capturers_[capture_id]->set_channel_id(-1);
+    return 0;
+  }
+  WEBRTC_STUB(StartCapture, (const int, const webrtc::CaptureCapability));
+  WEBRTC_STUB(StopCapture, (const int));
+  WEBRTC_STUB(SetRotateCapturedFrames, (const int,
+      const webrtc::RotateCapturedFrame));
+  WEBRTC_STUB(SetCaptureDelay, (const int, const unsigned int));
+  WEBRTC_STUB(NumberOfCapabilities, (const char*, const unsigned int));
+  WEBRTC_STUB(GetCaptureCapability, (const char*, const unsigned int,
+      const unsigned int, webrtc::CaptureCapability&));
+  WEBRTC_STUB(ShowCaptureSettingsDialogBox, (const char*, const unsigned int,
+      const char*, void*, const unsigned int, const unsigned int));
+  WEBRTC_STUB(GetOrientation, (const char*, webrtc::RotateCapturedFrame&));
+  WEBRTC_STUB(EnableBrightnessAlarm, (const int, const bool));
+  WEBRTC_STUB(RegisterObserver, (const int, webrtc::ViECaptureObserver&));
+  WEBRTC_STUB(DeregisterObserver, (const int));
+
+  // webrtc::ViENetwork
+  WEBRTC_STUB(SetLocalReceiver, (const int, const unsigned short,
+      const unsigned short, const char*));
+  WEBRTC_STUB(GetLocalReceiver, (const int, unsigned short&,
+      unsigned short&, char*));
+  WEBRTC_STUB(SetSendDestination, (const int, const char*, const unsigned short,
+      const unsigned short, const unsigned short, const unsigned short));
+  WEBRTC_STUB(GetSendDestination, (const int, char*, unsigned short&,
+      unsigned short&, unsigned short&, unsigned short&));
+  WEBRTC_STUB(RegisterSendTransport, (const int, webrtc::Transport&));
+  WEBRTC_STUB(DeregisterSendTransport, (const int));
+  WEBRTC_STUB(ReceivedRTPPacket, (const int, const void*, const int));
+  WEBRTC_STUB(ReceivedRTCPPacket, (const int, const void*, const int));
+  WEBRTC_STUB(GetSourceInfo, (const int, unsigned short&, unsigned short&,
+                              char*, unsigned int));
+  WEBRTC_STUB(GetLocalIP, (char*, bool));
+  WEBRTC_STUB(EnableIPv6, (int));
+  // Not using WEBRTC_STUB due to bool return value
+  virtual bool IsIPv6Enabled(int channel) { return true; }
+  WEBRTC_STUB(SetSourceFilter, (const int, const unsigned short,
+      const unsigned short, const char*));
+  WEBRTC_STUB(GetSourceFilter, (const int, unsigned short&,
+      unsigned short&, char*));
+  WEBRTC_STUB(SetSendToS, (const int, const int, const bool));
+  WEBRTC_STUB(GetSendToS, (const int, int&, bool&));
+  WEBRTC_STUB(SetSendGQoS, (const int, const bool, const int, const int));
+  WEBRTC_STUB(GetSendGQoS, (const int, bool&, int&, int&));
+  WEBRTC_STUB(SetMTU, (int, unsigned int));
+  WEBRTC_STUB(SetPacketTimeoutNotification, (const int, bool, int));
+  WEBRTC_STUB(RegisterObserver, (const int, webrtc::ViENetworkObserver&));
+  WEBRTC_STUB(SetPeriodicDeadOrAliveStatus, (const int, const bool,
+    const unsigned int));
+  WEBRTC_STUB(SendUDPPacket, (const int, const void*, const unsigned int,
+      int&, bool));
+
+  // webrtc::ViERender
+  WEBRTC_STUB(RegisterVideoRenderModule, (webrtc::VideoRender&));
+  WEBRTC_STUB(DeRegisterVideoRenderModule, (webrtc::VideoRender&));
+  WEBRTC_STUB(AddRenderer, (const int, void*, const unsigned int, const float,
+      const float, const float, const float));
+  WEBRTC_FUNC(RemoveRenderer, (const int render_id)) {
+    if (IsCapturerId(render_id)) {
+      WEBRTC_CHECK_CAPTURER(render_id);
+      return 0;
+    } else if (IsChannelId(render_id)) {
+      WEBRTC_CHECK_CHANNEL(render_id);
+      channels_[render_id]->has_renderer_ = false;
+      return 0;
+    }
+    return -1;
+  }
+  WEBRTC_FUNC(StartRender, (const int render_id)) {
+    if (IsCapturerId(render_id)) {
+      WEBRTC_CHECK_CAPTURER(render_id);
+      return 0;
+    } else if (IsChannelId(render_id)) {
+      WEBRTC_CHECK_CHANNEL(render_id);
+      channels_[render_id]->render_started_ = true;
+      return 0;
+    }
+    return -1;
+  }
+  WEBRTC_FUNC(StopRender, (const int render_id)) {
+    if (IsCapturerId(render_id)) {
+      WEBRTC_CHECK_CAPTURER(render_id);
+      return 0;
+    } else if (IsChannelId(render_id)) {
+      WEBRTC_CHECK_CHANNEL(render_id);
+      channels_[render_id]->render_started_ = false;
+      return 0;
+    }
+    return -1;
+  }
+  WEBRTC_STUB(ConfigureRender, (int, const unsigned int, const float,
+      const float, const float, const float));
+  WEBRTC_STUB(MirrorRenderStream, (const int, const bool, const bool,
+      const bool));
+  WEBRTC_FUNC(AddRenderer, (const int render_id,
+                            webrtc::RawVideoType video_type,
+                            webrtc::ExternalRenderer* renderer)) {
+    if (IsCapturerId(render_id)) {
+      WEBRTC_CHECK_CAPTURER(render_id);
+      return 0;
+    } else if (IsChannelId(render_id)) {
+      WEBRTC_CHECK_CHANNEL(render_id);
+      channels_[render_id]->has_renderer_ = true;
+      return 0;
+    }
+    return -1;
+  }
+
+  // webrtc::ViERTP_RTCP
+  WEBRTC_FUNC(SetLocalSSRC, (const int channel,
+                             const unsigned int ssrc,
+                             const webrtc::StreamType usage,
+                             const unsigned char simulcast_idx)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->local_ssrc_ = ssrc;
+    return 0;
+  }
+  WEBRTC_STUB_CONST(SetRemoteSSRCType, (const int,
+        const webrtc::StreamType, const unsigned int));
+
+  WEBRTC_FUNC_CONST(GetLocalSSRC, (const int channel,
+                                   unsigned int& ssrc)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    ssrc = channels_.find(channel)->second->local_ssrc_;
+    return 0;
+  }
+  WEBRTC_STUB_CONST(GetRemoteSSRC, (const int, unsigned int&));
+      WEBRTC_STUB_CONST(GetRemoteCSRCs, (const int, unsigned int*));
+  WEBRTC_STUB(SetStartSequenceNumber, (const int, unsigned short));
+  WEBRTC_FUNC(SetRTCPStatus,
+              (const int channel, const webrtc::ViERTCPMode mode)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->rtcp_status_ = mode;
+    return 0;
+  }
+  WEBRTC_STUB_CONST(GetRTCPStatus, (const int, webrtc::ViERTCPMode&));
+  WEBRTC_FUNC(SetRTCPCName, (const int channel,
+                             const char rtcp_cname[KMaxRTCPCNameLength])) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->cname_.assign(rtcp_cname);
+    return 0;
+  }
+  WEBRTC_FUNC_CONST(GetRTCPCName, (const int channel,
+                                   char rtcp_cname[KMaxRTCPCNameLength])) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    talk_base::strcpyn(rtcp_cname, KMaxRTCPCNameLength,
+                       channels_.find(channel)->second->cname_.c_str());
+    return 0;
+  }
+  WEBRTC_STUB_CONST(GetRemoteRTCPCName, (const int, char*));
+  WEBRTC_STUB(SendApplicationDefinedRTCPPacket, (const int, const unsigned char,
+      unsigned int, const char*, unsigned short));
+  WEBRTC_FUNC(SetNACKStatus, (const int channel, const bool enable)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->nack_ = enable;
+    channels_[channel]->hybrid_nack_fec_ = false;
+    return 0;
+  }
+  WEBRTC_STUB(SetFECStatus, (const int, const bool, const unsigned char,
+      const unsigned char));
+  WEBRTC_FUNC(SetHybridNACKFECStatus, (const int channel, const bool enable,
+      const unsigned char red_type, const unsigned char fec_type)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    if (red_type == fec_type ||
+        red_type == channels_[channel]->send_codec.plType ||
+        fec_type == channels_[channel]->send_codec.plType) {
+      return -1;
+    }
+    channels_[channel]->nack_ = false;
+    channels_[channel]->hybrid_nack_fec_ = true;
+    return 0;
+  }
+  WEBRTC_FUNC(SetKeyFrameRequestMethod,
+              (const int channel,
+               const webrtc::ViEKeyFrameRequestMethod method)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->key_frame_request_method_ = method;
+    return 0;
+  }
+  WEBRTC_BOOL_FUNC(SetRembStatus, (int channel, bool send, bool receive)) {
+    if (channels_.find(channel) == channels_.end())
+      return false;
+    channels_[channel]->remb_send_ = send;
+    channels_[channel]->remb_ = receive;
+    return true;
+  }
+  WEBRTC_FUNC(SetTMMBRStatus, (const int channel, const bool enable)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->tmmbr_ = enable;
+    return 0;
+  }
+  WEBRTC_STUB_CONST(GetReceivedRTCPStatistics, (const int, unsigned short&,
+      unsigned int&, unsigned int&, unsigned int&, int&));
+  WEBRTC_STUB_CONST(GetSentRTCPStatistics, (const int, unsigned short&,
+      unsigned int&, unsigned int&, unsigned int&, int&));
+  WEBRTC_STUB_CONST(GetRTPStatistics, (const int, unsigned int&, unsigned int&,
+      unsigned int&, unsigned int&));
+  WEBRTC_STUB_CONST(GetBandwidthUsage, (const int, unsigned int&,
+      unsigned int&, unsigned int&, unsigned int&));
+
+  WEBRTC_STUB(SetRTPKeepAliveStatus, (const int, bool, const char,
+      const unsigned int));
+  WEBRTC_STUB_CONST(GetRTPKeepAliveStatus,
+                    (const int, bool&, char&, unsigned int&));
+  WEBRTC_STUB(StartRTPDump, (const int, const char*, webrtc::RTPDirections));
+  WEBRTC_STUB(StopRTPDump, (const int, webrtc::RTPDirections));
+  WEBRTC_STUB(RegisterRTPObserver, (const int, webrtc::ViERTPObserver&));
+  WEBRTC_STUB(DeregisterRTPObserver, (const int));
+  WEBRTC_STUB(RegisterRTCPObserver, (const int, webrtc::ViERTCPObserver&));
+  WEBRTC_STUB(DeregisterRTCPObserver, (const int));
+
+  // webrtc::ViEImageProcess
+  WEBRTC_STUB(RegisterCaptureEffectFilter, (const int,
+      webrtc::ViEEffectFilter&));
+  WEBRTC_STUB(DeregisterCaptureEffectFilter, (const int));
+  WEBRTC_STUB(RegisterSendEffectFilter, (const int,
+      webrtc::ViEEffectFilter&));
+  WEBRTC_STUB(DeregisterSendEffectFilter, (const int));
+  WEBRTC_STUB(RegisterRenderEffectFilter, (const int,
+      webrtc::ViEEffectFilter&));
+  WEBRTC_STUB(DeregisterRenderEffectFilter, (const int));
+  WEBRTC_STUB(EnableDeflickering, (const int, const bool));
+  WEBRTC_STUB(EnableDenoising, (const int, const bool));
+  WEBRTC_STUB(EnableColorEnhancement, (const int, const bool));
+
+ private:
+  bool IsChannelId(int id) const {
+    return (id >= kViEChannelIdBase && id <= kViEChannelIdMax);
+  }
+  bool IsCapturerId(int id) const {
+    return (id >= kViECaptureIdBase && id <= kViECaptureIdMax);
+  }
+
+  bool inited_;
+  int last_channel_;
+  std::map<int, Channel*> channels_;
+  bool fail_create_channel_;
+  int last_capturer_;
+  std::map<int, Capturer*> capturers_;
+  bool fail_alloc_capturer_;
+  const cricket::VideoCodec* const* codecs_;
+  int num_codecs_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FAKEWEBRTCVIDEOENGINE_H_
diff --git a/talk/session/phone/fakewebrtcvoiceengine.h b/talk/session/phone/fakewebrtcvoiceengine.h
new file mode 100644
index 0000000..568d408
--- /dev/null
+++ b/talk/session/phone/fakewebrtcvoiceengine.h
@@ -0,0 +1,851 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKEWEBRTCVOICEENGINE_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCVOICEENGINE_H_
+
+#include <list>
+#include <map>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/fakewebrtccommon.h"
+#include "talk/session/phone/voiceprocessor.h"
+#include "talk/session/phone/webrtcvoe.h"
+
+namespace cricket {
+
+static const char kFakeDefaultDeviceName[] = "Fake Default";
+static const int kFakeDefaultDeviceId = -1;
+static const char kFakeDeviceName[] = "Fake Device";
+#ifdef WIN32
+static const int kFakeDeviceId = 0;
+#else
+static const int kFakeDeviceId = 1;
+#endif
+
+class FakeWebRtcVoiceEngine
+    : public webrtc::VoEAudioProcessing,
+      public webrtc::VoEBase, public webrtc::VoECodec, public webrtc::VoEDtmf,
+      public webrtc::VoEFile, public webrtc::VoEHardware,
+      public webrtc::VoEExternalMedia, public webrtc::VoENetEqStats,
+      public webrtc::VoENetwork, public webrtc::VoERTP_RTCP,
+      public webrtc::VoEVideoSync, public webrtc::VoEVolumeControl {
+ public:
+  struct Channel {
+    Channel()
+        : external_transport(false),
+          send(false),
+          playout(false),
+          file(false),
+          vad(false),
+          fec(false),
+          cn8_type(13),
+          cn16_type(105),
+          dtmf_type(106),
+          fec_type(117),
+          send_ssrc(0),
+          level_header_ext_(-1) {
+      memset(&send_codec, 0, sizeof(send_codec));
+    }
+    bool external_transport;
+    bool send;
+    bool playout;
+    bool file;
+    bool vad;
+    bool fec;
+    int cn8_type;
+    int cn16_type;
+    int dtmf_type;
+    int fec_type;
+    uint32 send_ssrc;
+    int level_header_ext_;
+    std::vector<webrtc::CodecInst> recv_codecs;
+    webrtc::CodecInst send_codec;
+    std::list<std::string> packets;
+  };
+
+  FakeWebRtcVoiceEngine(const cricket::AudioCodec* const* codecs,
+                        int num_codecs)
+      : inited_(false),
+        last_channel_(-1),
+        fail_create_channel_(false),
+        codecs_(codecs),
+        num_codecs_(num_codecs),
+        ec_enabled_(false),
+        ns_enabled_(false),
+        ec_mode_(webrtc::kEcDefault),
+        ns_mode_(webrtc::kNsDefault),
+        observer_(NULL),
+        playout_fail_channel_(-1),
+        send_fail_channel_(-1),
+        fail_start_recording_microphone_(false),
+        recording_microphone_(false),
+        media_processor_(NULL) {
+    memset(&agc_config_, 0, sizeof(agc_config_));
+  }
+  ~FakeWebRtcVoiceEngine() {
+    // Ought to have all been deleted by the WebRtcVoiceMediaChannel
+    // destructors, but just in case ...
+    for (std::map<int, Channel*>::const_iterator i = channels_.begin();
+         i != channels_.end(); ++i) {
+      delete i->second;
+    }
+  }
+  bool IsExternalMediaProcessorRegistered() const {
+    return media_processor_ != NULL;
+  }
+  bool IsInited() const { return inited_; }
+  int GetLastChannel() const { return last_channel_; }
+  int GetNumChannels() const { return channels_.size(); }
+  bool GetPlayout(int channel) {
+    return channels_[channel]->playout;
+  }
+  bool GetSend(int channel) {
+    return channels_[channel]->send;
+  }
+  bool GetRecordingMicrophone() {
+    return recording_microphone_;
+  }
+  bool GetVAD(int channel) {
+    return channels_[channel]->vad;
+  }
+  bool GetFEC(int channel) {
+    return channels_[channel]->fec;
+  }
+  int GetSendCNPayloadType(int channel, bool wideband) {
+    return (wideband) ?
+        channels_[channel]->cn16_type :
+        channels_[channel]->cn8_type;
+  }
+  int GetSendTelephoneEventPayloadType(int channel) {
+    return channels_[channel]->dtmf_type;
+  }
+  int GetSendFECPayloadType(int channel) {
+    return channels_[channel]->fec_type;
+  }
+  bool CheckPacket(int channel, const void* data, size_t len) {
+    bool result = !CheckNoPacket(channel);
+    if (result) {
+      std::string packet = channels_[channel]->packets.front();
+      result = (packet == std::string(static_cast<const char*>(data), len));
+      channels_[channel]->packets.pop_front();
+    }
+    return result;
+  }
+  bool CheckNoPacket(int channel) {
+    return channels_[channel]->packets.empty();
+  }
+  void TriggerCallbackOnError(int channel_num, int err_code) {
+    ASSERT(observer_ != NULL);
+    observer_->CallbackOnError(channel_num, err_code);
+  }
+  void set_playout_fail_channel(int channel) {
+    playout_fail_channel_ = channel;
+  }
+  void set_send_fail_channel(int channel) {
+    send_fail_channel_ = channel;
+  }
+  void set_fail_start_recording_microphone(
+      bool fail_start_recording_microphone) {
+    fail_start_recording_microphone_ = fail_start_recording_microphone;
+  }
+  void set_fail_create_channel(bool fail_create_channel) {
+    fail_create_channel_ = fail_create_channel;
+  }
+  void TriggerProcessPacket(MediaProcessorDirection direction) {
+    webrtc::ProcessingTypes pt =
+        (direction == cricket::MPD_TX) ?
+            webrtc::kRecordingPerChannel : webrtc::kPlaybackPerChannel;
+    if (media_processor_ != NULL) {
+      media_processor_->Process(0,
+                                pt,
+                                NULL,
+                                0,
+                                0,
+                                true);
+    }
+  }
+
+  WEBRTC_STUB(Release, ());
+
+  // webrtc::VoEBase
+  WEBRTC_FUNC(RegisterVoiceEngineObserver, (
+      webrtc::VoiceEngineObserver& observer)) {
+    observer_ = &observer;
+    return 0;
+  }
+  WEBRTC_STUB(DeRegisterVoiceEngineObserver, ());
+  WEBRTC_STUB(RegisterAudioDeviceModule, (webrtc::AudioDeviceModule& adm));
+  WEBRTC_STUB(DeRegisterAudioDeviceModule, ());
+
+  WEBRTC_FUNC(Init, (webrtc::AudioDeviceModule* adm)) {
+    inited_ = true;
+    return 0;
+  }
+  WEBRTC_FUNC(Terminate, ()) {
+    inited_ = false;
+    return 0;
+  }
+  WEBRTC_STUB(MaxNumOfChannels, ());
+  WEBRTC_FUNC(CreateChannel, ()) {
+    if (fail_create_channel_) {
+      return -1;
+    }
+    Channel* ch = new Channel();
+    for (int i = 0; i < NumOfCodecs(); ++i) {
+      webrtc::CodecInst codec;
+      GetCodec(i, codec);
+      ch->recv_codecs.push_back(codec);
+    }
+    channels_[++last_channel_] = ch;
+    return last_channel_;
+  }
+  WEBRTC_FUNC(DeleteChannel, (int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    delete channels_[channel];
+    channels_.erase(channel);
+    return 0;
+  }
+  WEBRTC_STUB(SetLocalReceiver, (int channel, int port, int RTCPport,
+                                 const char ipaddr[64],
+                                 const char multiCastAddr[64]));
+  WEBRTC_STUB(GetLocalReceiver, (int channel, int& port, int& RTCPport,
+                                 char ipaddr[64]));
+  WEBRTC_STUB(SetSendDestination, (int channel, int port,
+                                   const char ipaddr[64],
+                                   int sourcePort, int RTCPport));
+  WEBRTC_STUB(GetSendDestination, (int channel, int& port, char ipaddr[64],
+                                   int& sourcePort, int& RTCPport));
+  WEBRTC_STUB(StartReceive, (int channel));
+  WEBRTC_FUNC(StartPlayout, (int channel)) {
+    if (playout_fail_channel_ != channel) {
+      WEBRTC_CHECK_CHANNEL(channel);
+      channels_[channel]->playout = true;
+      return 0;
+    } else {
+      // When playout_fail_channel_ == channel, fail the StartPlayout on this
+      // channel.
+      return -1;
+    }
+  }
+  WEBRTC_FUNC(StartSend, (int channel)) {
+    if (send_fail_channel_ != channel) {
+      WEBRTC_CHECK_CHANNEL(channel);
+      channels_[channel]->send = true;
+      return 0;
+    } else {
+      // When send_fail_channel_ == channel, fail the StartSend on this
+      // channel.
+      return -1;
+    }
+  }
+  WEBRTC_STUB(StopReceive, (int channel));
+  WEBRTC_FUNC(StopPlayout, (int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->playout = false;
+    return 0;
+  }
+  WEBRTC_FUNC(StopSend, (int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->send = false;
+    return 0;
+  }
+  WEBRTC_STUB(GetVersion, (char version[1024]));
+  WEBRTC_STUB(LastError, ());
+  WEBRTC_STUB(SetOnHoldStatus, (int, bool, webrtc::OnHoldModes));
+  WEBRTC_STUB(GetOnHoldStatus, (int, bool&, webrtc::OnHoldModes&));
+  WEBRTC_STUB(SetNetEQPlayoutMode, (int, webrtc::NetEqModes));
+  WEBRTC_STUB(GetNetEQPlayoutMode, (int, webrtc::NetEqModes&));
+  WEBRTC_STUB(SetNetEQBGNMode, (int, webrtc::NetEqBgnModes));
+  WEBRTC_STUB(GetNetEQBGNMode, (int, webrtc::NetEqBgnModes&));
+
+  // webrtc::VoECodec
+  WEBRTC_FUNC(NumOfCodecs, ()) {
+    return num_codecs_;
+  }
+  WEBRTC_FUNC(GetCodec, (int index, webrtc::CodecInst& codec)) {
+    if (index < 0 || index >= NumOfCodecs()) {
+      return -1;
+    }
+    const cricket::AudioCodec& c(*codecs_[index]);
+    codec.pltype = c.id;
+    talk_base::strcpyn(codec.plname, sizeof(codec.plname), c.name.c_str());
+    codec.plfreq = c.clockrate;
+    codec.pacsize = 0;
+    codec.channels = c.channels;
+    codec.rate = c.bitrate;
+    return 0;
+  }
+  WEBRTC_FUNC(SetSendCodec, (int channel, const webrtc::CodecInst& codec)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->send_codec = codec;
+    return 0;
+  }
+  WEBRTC_FUNC(GetSendCodec, (int channel, webrtc::CodecInst& codec)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    codec = channels_[channel]->send_codec;
+    return 0;
+  }
+  WEBRTC_STUB(GetRecCodec, (int channel, webrtc::CodecInst& codec));
+  WEBRTC_STUB(SetAMREncFormat, (int channel, webrtc::AmrMode mode));
+  WEBRTC_STUB(SetAMRDecFormat, (int channel, webrtc::AmrMode mode));
+  WEBRTC_STUB(SetAMRWbEncFormat, (int channel, webrtc::AmrMode mode));
+  WEBRTC_STUB(SetAMRWbDecFormat, (int channel, webrtc::AmrMode mode));
+  WEBRTC_STUB(SetISACInitTargetRate, (int channel, int rateBps,
+                                      bool useFixedFrameSize));
+  WEBRTC_STUB(SetISACMaxRate, (int channel, int rateBps));
+  WEBRTC_STUB(SetISACMaxPayloadSize, (int channel, int sizeBytes));
+  WEBRTC_FUNC(SetRecPayloadType, (int channel,
+                                  const webrtc::CodecInst& codec)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    Channel* ch = channels_[channel];
+    // Check if something else already has this slot.
+    if (codec.pltype != -1) {
+      for (std::vector<webrtc::CodecInst>::iterator it =
+          ch->recv_codecs.begin(); it != ch->recv_codecs.end(); ++it) {
+        if (it->pltype == codec.pltype) {
+          return -1;
+        }
+      }
+    }
+    // Otherwise try to find this codec and update its payload type.
+    for (std::vector<webrtc::CodecInst>::iterator it = ch->recv_codecs.begin();
+         it != ch->recv_codecs.end(); ++it) {
+      if (strcmp(it->plname, codec.plname) == 0 &&
+          it->plfreq == codec.plfreq) {
+        it->pltype = codec.pltype;
+        return 0;
+      }
+    }
+    return -1;  // not found
+  }
+  WEBRTC_FUNC(SetSendCNPayloadType, (int channel, int type,
+                                     webrtc::PayloadFrequencies frequency)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    if (frequency == webrtc::kFreq8000Hz) {
+      channels_[channel]->cn8_type = type;
+    } else if (frequency == webrtc::kFreq16000Hz) {
+      channels_[channel]->cn16_type = type;
+    }
+    return 0;
+  }
+  WEBRTC_FUNC(GetRecPayloadType, (int channel, webrtc::CodecInst& codec)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    Channel* ch = channels_[channel];
+    for (std::vector<webrtc::CodecInst>::iterator it = ch->recv_codecs.begin();
+         it != ch->recv_codecs.end(); ++it) {
+      if (strcmp(it->plname, codec.plname) == 0 &&
+          it->plfreq == codec.plfreq &&
+          it->pltype != -1) {
+        codec.pltype = it->pltype;
+        return 0;
+      }
+    }
+    return -1;  // not found
+  }
+  WEBRTC_FUNC(SetVADStatus, (int channel, bool enable, webrtc::VadModes mode,
+                             bool disableDTX)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->vad = enable;
+    return 0;
+  }
+  WEBRTC_STUB(GetVADStatus, (int channel, bool& enabled,
+                             webrtc::VadModes& mode, bool& disabledDTX));
+
+  // webrtc::VoEDtmf
+  WEBRTC_STUB(SendTelephoneEvent, (int channel, int eventCode,
+      bool outOfBand = true, int lengthMs = 160, int attenuationDb = 10));
+
+  WEBRTC_FUNC(SetSendTelephoneEventPayloadType,
+      (int channel, unsigned char type)) {
+    channels_[channel]->dtmf_type = type;
+    return 0;
+  };
+  WEBRTC_STUB(GetSendTelephoneEventPayloadType,
+      (int channel, unsigned char& type));
+
+  WEBRTC_STUB(SetDtmfFeedbackStatus, (bool enable, bool directFeedback));
+  WEBRTC_STUB(GetDtmfFeedbackStatus, (bool& enabled, bool& directFeedback));
+  WEBRTC_STUB(RegisterTelephoneEventDetection, (int channel,
+      webrtc::TelephoneEventDetectionMethods detectionMethod,
+      webrtc::VoETelephoneEventObserver& observer));
+  WEBRTC_STUB(DeRegisterTelephoneEventDetection, (int channel));
+  WEBRTC_STUB(SetDtmfPlayoutStatus, (int channel, bool enable));
+  WEBRTC_STUB(GetDtmfPlayoutStatus, (int channel, bool& enabled));
+
+
+  WEBRTC_STUB(PlayDtmfTone,
+      (int eventCode, int lengthMs = 200, int attenuationDb = 10));
+  WEBRTC_STUB(StartPlayingDtmfTone,
+      (int eventCode, int attenuationDb = 10));
+  WEBRTC_STUB(StopPlayingDtmfTone, ());
+  WEBRTC_STUB(GetTelephoneEventDetectionStatus, (int channel,
+      bool& enabled, webrtc::TelephoneEventDetectionMethods& detectionMethod));
+
+  // webrtc::VoEFile
+  WEBRTC_FUNC(StartPlayingFileLocally, (int channel, const char* fileNameUTF8,
+                                        bool loop, webrtc::FileFormats format,
+                                        float volumeScaling, int startPointMs,
+                                        int stopPointMs)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->file = true;
+    return 0;
+  }
+  WEBRTC_FUNC(StartPlayingFileLocally, (int channel, webrtc::InStream* stream,
+                                        webrtc::FileFormats format,
+                                        float volumeScaling, int startPointMs,
+                                        int stopPointMs)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->file = true;
+    return 0;
+  }
+  WEBRTC_FUNC(StopPlayingFileLocally, (int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->file = false;
+    return 0;
+  }
+  WEBRTC_FUNC(IsPlayingFileLocally, (int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    return (channels_[channel]->file) ? 1 : 0;
+  }
+  WEBRTC_STUB(ScaleLocalFilePlayout, (int channel, float scale));
+  WEBRTC_STUB(StartPlayingFileAsMicrophone, (int channel,
+                                             const char* fileNameUTF8,
+                                             bool loop,
+                                             bool mixWithMicrophone,
+                                             webrtc::FileFormats format,
+                                             float volumeScaling));
+  WEBRTC_STUB(StartPlayingFileAsMicrophone, (int channel,
+                                             webrtc::InStream* stream,
+                                             bool mixWithMicrophone,
+                                             webrtc::FileFormats format,
+                                             float volumeScaling));
+  WEBRTC_STUB(StopPlayingFileAsMicrophone, (int channel));
+  WEBRTC_STUB(IsPlayingFileAsMicrophone, (int channel));
+  WEBRTC_STUB(ScaleFileAsMicrophonePlayout, (int channel, float scale));
+  WEBRTC_STUB(StartRecordingPlayout, (int channel, const char* fileNameUTF8,
+                                      webrtc::CodecInst* compression,
+                                      int maxSizeBytes));
+  WEBRTC_STUB(StartRecordingPlayout, (int channel, webrtc::OutStream* stream,
+                                      webrtc::CodecInst* compression));
+  WEBRTC_STUB(StopRecordingPlayout, (int channel));
+  WEBRTC_FUNC(StartRecordingMicrophone, (const char* fileNameUTF8,
+                                         webrtc::CodecInst* compression,
+                                         int maxSizeBytes)) {
+    if (fail_start_recording_microphone_) {
+      return -1;
+    }
+    recording_microphone_ = true;
+    return 0;
+  }
+  WEBRTC_FUNC(StartRecordingMicrophone, (webrtc::OutStream* stream,
+                                         webrtc::CodecInst* compression)) {
+    if (fail_start_recording_microphone_) {
+      return -1;
+    }
+    recording_microphone_ = true;
+    return 0;
+  }
+  WEBRTC_FUNC(StopRecordingMicrophone, ()) {
+    if (!recording_microphone_) {
+      return -1;
+    }
+    recording_microphone_ = false;
+    return 0;
+  }
+  WEBRTC_STUB(ConvertPCMToWAV, (const char* fileNameInUTF8,
+                                const char* fileNameOutUTF8));
+  WEBRTC_STUB(ConvertPCMToWAV, (webrtc::InStream* streamIn,
+                                webrtc::OutStream* streamOut));
+  WEBRTC_STUB(ConvertWAVToPCM, (const char* fileNameInUTF8,
+                                const char* fileNameOutUTF8));
+  WEBRTC_STUB(ConvertWAVToPCM, (webrtc::InStream* streamIn,
+                                webrtc::OutStream* streamOut));
+  WEBRTC_STUB(ConvertPCMToCompressed, (const char* fileNameInUTF8,
+                                       const char* fileNameOutUTF8,
+                                       webrtc::CodecInst* compression));
+  WEBRTC_STUB(ConvertPCMToCompressed, (webrtc::InStream* streamIn,
+                                       webrtc::OutStream* streamOut,
+                                       webrtc::CodecInst* compression));
+  WEBRTC_STUB(ConvertCompressedToPCM, (const char* fileNameInUTF8,
+                                     const char* fileNameOutUTF8));
+  WEBRTC_STUB(ConvertCompressedToPCM, (webrtc::InStream* streamIn,
+                                       webrtc::OutStream* streamOut));
+  WEBRTC_STUB(GetFileDuration, (const char* fileNameUTF8, int& durationMs,
+                                webrtc::FileFormats format));
+  WEBRTC_STUB(GetPlaybackPosition, (int channel, int& positionMs));
+
+  // webrtc::VoEHardware
+  WEBRTC_STUB(GetCPULoad, (int&));
+  WEBRTC_STUB(GetSystemCPULoad, (int&));
+  WEBRTC_FUNC(GetNumOfRecordingDevices, (int& num)) {
+    return GetNumDevices(num);
+  }
+  WEBRTC_FUNC(GetNumOfPlayoutDevices, (int& num)) {
+    return GetNumDevices(num);
+  }
+  WEBRTC_FUNC(GetRecordingDeviceName, (int i, char* name, char* guid)) {
+    return GetDeviceName(i, name, guid);
+  }
+  WEBRTC_FUNC(GetPlayoutDeviceName, (int i, char* name, char* guid)) {
+    return GetDeviceName(i, name, guid);
+  }
+  WEBRTC_STUB(SetRecordingDevice, (int, webrtc::StereoChannel));
+  WEBRTC_STUB(SetPlayoutDevice, (int));
+  WEBRTC_STUB(SetAudioDeviceLayer, (webrtc::AudioLayers));
+  WEBRTC_STUB(GetAudioDeviceLayer, (webrtc::AudioLayers&));
+  WEBRTC_STUB(GetPlayoutDeviceStatus, (bool&));
+  WEBRTC_STUB(GetRecordingDeviceStatus, (bool&));
+  WEBRTC_STUB(ResetAudioDevice, ());
+  WEBRTC_STUB(AudioDeviceControl, (unsigned int, unsigned int, unsigned int));
+  WEBRTC_STUB(NeedMorePlayData, (short int*, int, int, int, int&));
+  WEBRTC_STUB(RecordedDataIsAvailable, (short int*, int, int, int, int&));
+  WEBRTC_STUB(GetDevice, (char*, unsigned int));
+  WEBRTC_STUB(GetPlatform, (char*, unsigned int));
+  WEBRTC_STUB(GetOS, (char*, unsigned int));
+  WEBRTC_STUB(SetGrabPlayout, (bool));
+  WEBRTC_STUB(SetGrabRecording, (bool));
+  WEBRTC_STUB(SetLoudspeakerStatus, (bool enable));
+  WEBRTC_STUB(GetLoudspeakerStatus, (bool& enabled));
+  WEBRTC_STUB(EnableBuiltInAEC, (bool enable));
+  virtual bool BuiltInAECIsEnabled() const { return true; }
+  WEBRTC_STUB(SetSamplingRate, (int));
+  WEBRTC_STUB(GetSamplingRate, (int&));
+
+  // webrtc::VoENetEqStats
+  WEBRTC_STUB(GetNetworkStatistics, (int, webrtc::NetworkStatistics&));
+  WEBRTC_STUB(GetPreferredBufferSize, (int, short unsigned int&));
+  WEBRTC_STUB(ResetJitterStatistics, (int));
+
+  // webrtc::VoENetwork
+  WEBRTC_FUNC(RegisterExternalTransport, (int channel,
+                                          webrtc::Transport& transport)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->external_transport = true;
+    return 0;
+  }
+  WEBRTC_FUNC(DeRegisterExternalTransport, (int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->external_transport = false;
+    return 0;
+  }
+  WEBRTC_FUNC(ReceivedRTPPacket, (int channel, const void* data,
+                                  unsigned int length)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    if (!channels_[channel]->external_transport) return -1;
+    channels_[channel]->packets.push_back(
+        std::string(static_cast<const char*>(data), length));
+    return 0;
+  }
+  WEBRTC_STUB(ReceivedRTCPPacket, (int channel, const void* data,
+                                   unsigned int length));
+  WEBRTC_STUB(GetSourceInfo, (int channel, int& rtpPort, int& rtcpPort,
+                              char ipaddr[64]));
+  WEBRTC_STUB(GetLocalIP, (char ipaddr[64], bool ipv6));
+  WEBRTC_STUB(EnableIPv6, (int channel));
+  // Not using WEBRTC_STUB due to bool return value
+  virtual bool IPv6IsEnabled(int channel) { return true; }
+  WEBRTC_STUB(SetSourceFilter, (int channel, int rtpPort, int rtcpPort,
+                                const char ipaddr[64]));
+  WEBRTC_STUB(GetSourceFilter, (int channel, int& rtpPort, int& rtcpPort,
+                                char ipaddr[64]));
+  WEBRTC_STUB(SetSendTOS, (int channel, int priority,
+                           int DSCP, bool useSetSockopt));
+  WEBRTC_STUB(GetSendTOS, (int channel, int& priority,
+                           int& DSCP, bool& useSetSockopt));
+  WEBRTC_STUB(SetSendGQoS, (int channel, bool enable, int serviceType,
+                            int overrideDSCP));
+  WEBRTC_STUB(GetSendGQoS, (int channel, bool& enabled, int& serviceType,
+                            int& overrideDSCP));
+  WEBRTC_STUB(SetPacketTimeoutNotification, (int channel, bool enable,
+                                             int timeoutSeconds));
+  WEBRTC_STUB(GetPacketTimeoutNotification, (int channel, bool& enable,
+                                             int& timeoutSeconds));
+  WEBRTC_STUB(RegisterDeadOrAliveObserver, (int channel,
+      webrtc::VoEConnectionObserver& observer));
+  WEBRTC_STUB(DeRegisterDeadOrAliveObserver, (int channel));
+  WEBRTC_STUB(GetPeriodicDeadOrAliveStatus, (int channel, bool& enabled,
+                                             int& sampleTimeSeconds));
+  WEBRTC_STUB(SetPeriodicDeadOrAliveStatus, (int channel, bool enable,
+                                             int sampleTimeSeconds));
+  WEBRTC_STUB(SendUDPPacket, (int channel, const void* data,
+                              unsigned int length, int& transmittedBytes,
+                              bool useRtcpSocket));
+
+  // webrtc::VoERTP_RTCP
+  WEBRTC_STUB(RegisterRTPObserver, (int channel,
+                                    webrtc::VoERTPObserver& observer));
+  WEBRTC_STUB(DeRegisterRTPObserver, (int channel));
+  WEBRTC_STUB(RegisterRTCPObserver, (int channel,
+                                     webrtc::VoERTCPObserver& observer));
+  WEBRTC_STUB(DeRegisterRTCPObserver, (int channel));
+  WEBRTC_FUNC(SetLocalSSRC, (int channel, unsigned int ssrc)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->send_ssrc = ssrc;
+    return 0;
+  }
+  WEBRTC_FUNC(GetLocalSSRC, (int channel, unsigned int& ssrc)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    ssrc = channels_[channel]->send_ssrc;
+    return 0;
+  }
+  WEBRTC_STUB(GetRemoteSSRC, (int channel, unsigned int& ssrc));
+  WEBRTC_FUNC(SetRTPAudioLevelIndicationStatus, (int channel, bool enable,
+      unsigned char ID)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->level_header_ext_ = (enable) ? ID : -1;
+    return 0;
+  }
+  WEBRTC_FUNC(GetRTPAudioLevelIndicationStatus, (int channel, bool& enabled,
+      unsigned char& ID)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    enabled = (channels_[channel]->level_header_ext_ != -1);
+    ID = channels_[channel]->level_header_ext_;
+    return 0;
+  }
+  WEBRTC_STUB(GetRemoteCSRCs, (int channel, unsigned int arrCSRC[15]));
+  WEBRTC_STUB(GetRemoteEnergy, (int channel, unsigned char arrEnergy[15]));
+  WEBRTC_STUB(SetRTCPStatus, (int channel, bool enable));
+  WEBRTC_STUB(GetRTCPStatus, (int channel, bool& enabled));
+  WEBRTC_STUB(SetRTCP_CNAME, (int channel, const char cname[256]));
+  WEBRTC_STUB(GetRTCP_CNAME, (int channel, char cname[256]));
+  WEBRTC_STUB(GetRemoteRTCP_CNAME, (int channel, char* cname));
+  WEBRTC_STUB(GetRemoteRTCPData, (int channel, unsigned int& NTPHigh,
+                                  unsigned int& NTPLow,
+                                  unsigned int& timestamp,
+                                  unsigned int& playoutTimestamp,
+                                  unsigned int* jitter,
+                                  unsigned short* fractionLost));
+  WEBRTC_STUB(SendApplicationDefinedRTCPPacket, (int channel,
+                                                 const unsigned char subType,
+                                                 unsigned int name,
+                                                 const char* data,
+                                                 unsigned short dataLength));
+  WEBRTC_STUB(GetRTPStatistics, (int channel, unsigned int& averageJitterMs,
+                                 unsigned int& maxJitterMs,
+                                 unsigned int& discardedPackets));
+  WEBRTC_STUB(GetRTCPStatistics, (int channel, unsigned short& fractionLost,
+                                  unsigned int& cumulativeLost,
+                                  unsigned int& extendedMax,
+                                  unsigned int& jitterSamples, int& rttMs));
+  WEBRTC_STUB(GetRTCPStatistics, (int channel, webrtc::CallStatistics& stats));
+  WEBRTC_FUNC(SetFECStatus, (int channel, bool enable, int redPayloadtype)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->fec = enable;
+    channels_[channel]->fec_type = redPayloadtype;
+    return 0;
+  }
+  WEBRTC_FUNC(GetFECStatus, (int channel, bool& enable, int& redPayloadtype)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    enable = channels_[channel]->fec;
+    redPayloadtype = channels_[channel]->fec_type;
+    return 0;
+  }
+  WEBRTC_STUB(SetRTPKeepaliveStatus, (int channel, bool enable,
+                                      unsigned char unknownPayloadType,
+                                      int deltaTransmitTimeSeconds));
+  WEBRTC_STUB(GetRTPKeepaliveStatus, (int channel, bool& enabled,
+                                      unsigned char& unknownPayloadType,
+                                      int& deltaTransmitTimeSeconds));
+  WEBRTC_STUB(StartRTPDump, (int channel, const char* fileNameUTF8,
+                             webrtc::RTPDirections direction));
+  WEBRTC_STUB(StopRTPDump, (int channel, webrtc::RTPDirections direction));
+  WEBRTC_STUB(RTPDumpIsActive, (int channel, webrtc::RTPDirections direction));
+  WEBRTC_STUB(InsertExtraRTPPacket, (int channel, unsigned char payloadType,
+                                     bool markerBit, const char* payloadData,
+                                     unsigned short payloadSize));
+
+  // webrtc::VoEVideoSync
+  WEBRTC_STUB(GetPlayoutBufferSize, (int& bufferMs));
+  WEBRTC_STUB(GetPlayoutTimestamp, (int channel, unsigned int& timestamp));
+  WEBRTC_STUB(GetRtpRtcp, (int, webrtc::RtpRtcp*&));
+  WEBRTC_STUB(SetInitTimestamp, (int channel, unsigned int timestamp));
+  WEBRTC_STUB(SetInitSequenceNumber, (int channel, short sequenceNumber));
+  WEBRTC_STUB(SetMinimumPlayoutDelay, (int channel, int delayMs));
+  WEBRTC_STUB(GetDelayEstimate, (int channel, int& delayMs));
+  WEBRTC_STUB(GetSoundcardBufferSize, (int& bufferMs));
+
+  // webrtc::VoEVolumeControl
+  WEBRTC_STUB(SetSpeakerVolume, (unsigned int));
+  WEBRTC_STUB(GetSpeakerVolume, (unsigned int&));
+  WEBRTC_STUB(SetSystemOutputMute, (bool));
+  WEBRTC_STUB(GetSystemOutputMute, (bool&));
+  WEBRTC_STUB(SetMicVolume, (unsigned int));
+  WEBRTC_STUB(GetMicVolume, (unsigned int&));
+  WEBRTC_STUB(SetInputMute, (int, bool));
+  WEBRTC_STUB(GetInputMute, (int, bool&));
+  WEBRTC_STUB(SetSystemInputMute, (bool));
+  WEBRTC_STUB(GetSystemInputMute, (bool&));
+  WEBRTC_STUB(GetSpeechInputLevel, (unsigned int&));
+  WEBRTC_STUB(GetSpeechOutputLevel, (int, unsigned int&));
+  WEBRTC_STUB(GetSpeechInputLevelFullRange, (unsigned int&));
+  WEBRTC_STUB(GetSpeechOutputLevelFullRange, (int, unsigned int&));
+  WEBRTC_STUB(SetChannelOutputVolumeScaling, (int, float));
+  WEBRTC_STUB(GetChannelOutputVolumeScaling, (int, float&));
+  WEBRTC_STUB(SetOutputVolumePan, (int, float, float));
+  WEBRTC_STUB(GetOutputVolumePan, (int, float&, float&));
+
+  // webrtc::VoEAudioProcessing
+  WEBRTC_FUNC(SetNsStatus, (bool enable, webrtc::NsModes mode)) {
+    ns_enabled_ = enable;
+    ns_mode_ = mode;
+    return 0;
+  }
+  WEBRTC_FUNC(GetNsStatus, (bool& enabled, webrtc::NsModes& mode)) {
+    enabled = ns_enabled_;
+    mode = ns_mode_;
+    return 0;
+  }
+  WEBRTC_STUB(SetAgcStatus, (bool enable, webrtc::AgcModes mode));
+  WEBRTC_STUB(GetAgcStatus, (bool& enabled, webrtc::AgcModes& mode));
+
+  WEBRTC_FUNC(SetAgcConfig, (const webrtc::AgcConfig config)) {
+    agc_config_ = config;
+    return 0;
+  }
+  WEBRTC_FUNC(GetAgcConfig, (webrtc::AgcConfig& config)) {
+    config = agc_config_;
+    return 0;
+  }
+  WEBRTC_FUNC(SetEcStatus, (bool enable, webrtc::EcModes mode)) {
+    ec_enabled_ = enable;
+    ec_mode_ = mode;
+    return 0;
+  }
+  WEBRTC_FUNC(GetEcStatus, (bool& enabled, webrtc::EcModes& mode)) {
+    enabled = ec_enabled_;
+    mode = ec_mode_;
+    return 0;
+  }
+  WEBRTC_STUB(SetAecmMode, (webrtc::AecmModes mode, bool enableCNG));
+  WEBRTC_STUB(GetAecmMode, (webrtc::AecmModes& mode, bool& enabledCNG));
+  WEBRTC_STUB(SetRxNsStatus, (int channel, bool enable, webrtc::NsModes mode));
+  WEBRTC_STUB(GetRxNsStatus, (int channel, bool& enabled,
+                              webrtc::NsModes& mode));
+  WEBRTC_STUB(SetRxAgcStatus, (int channel, bool enable,
+                               webrtc::AgcModes mode));
+  WEBRTC_STUB(GetRxAgcStatus, (int channel, bool& enabled,
+                               webrtc::AgcModes& mode));
+  WEBRTC_STUB(SetRxAgcConfig, (int channel, const webrtc::AgcConfig config));
+  WEBRTC_STUB(GetRxAgcConfig, (int channel, webrtc::AgcConfig& config));
+
+  WEBRTC_STUB(RegisterRxVadObserver, (int, webrtc::VoERxVadCallback&));
+  WEBRTC_STUB(DeRegisterRxVadObserver, (int channel));
+  WEBRTC_STUB(VoiceActivityIndicator, (int channel));
+  WEBRTC_STUB(SetEcMetricsStatus, (bool enable));
+  WEBRTC_STUB(GetEcMetricsStatus, (bool& enable));
+  WEBRTC_STUB(GetEchoMetrics, (int& ERL, int& ERLE, int& RERL, int& A_NLP));
+  WEBRTC_STUB(GetEcDelayMetrics, (int& delay_median, int& delay_std));
+
+  WEBRTC_STUB(StartDebugRecording, (const char* fileNameUTF8));
+  WEBRTC_STUB(StopDebugRecording, ());
+
+  WEBRTC_STUB(SetTypingDetectionStatus, (bool enable));
+  WEBRTC_STUB(GetTypingDetectionStatus, (bool& enabled));
+
+  // webrtc::VoEExternalMedia
+  WEBRTC_FUNC(RegisterExternalMediaProcessing,
+              (int channel, webrtc::ProcessingTypes type,
+               webrtc::VoEMediaProcess& processObject)) {
+    media_processor_ = &processObject;
+    return 0;
+  }
+  WEBRTC_FUNC(DeRegisterExternalMediaProcessing,
+              (int channel, webrtc::ProcessingTypes type)) {
+    media_processor_ = NULL;
+    return 0;
+  }
+  WEBRTC_STUB(SetExternalRecordingStatus, (bool enable));
+  WEBRTC_STUB(SetExternalPlayoutStatus, (bool enable));
+  WEBRTC_STUB(ExternalRecordingInsertData,
+              (const WebRtc_Word16 speechData10ms[], int lengthSamples,
+               int samplingFreqHz, int current_delay_ms));
+  WEBRTC_STUB(ExternalPlayoutGetData,
+              (WebRtc_Word16 speechData10ms[], int samplingFreqHz,
+               int current_delay_ms, int& lengthSamples));
+
+ private:
+  int GetNumDevices(int& num) {
+#ifdef WIN32
+    num = 1;
+#else
+    // On non-Windows platforms VE adds a special entry for the default device,
+    // so if there is one physical device then there are two entries in the
+    // list.
+    num = 2;
+#endif
+    return 0;
+  }
+
+  int GetDeviceName(int i, char* name, char* guid) {
+    const char *s;
+#ifdef WIN32
+    if (0 == i) {
+      s = kFakeDeviceName;
+    } else {
+      return -1;
+    }
+#else
+    // See comment above.
+    if (0 == i) {
+      s = kFakeDefaultDeviceName;
+    } else if (1 == i) {
+      s = kFakeDeviceName;
+    } else {
+      return -1;
+    }
+#endif
+    strcpy(name, s);
+    guid[0] = '\0';
+    return 0;
+  }
+
+  bool inited_;
+  int last_channel_;
+  std::map<int, Channel*> channels_;
+  bool fail_create_channel_;
+  const cricket::AudioCodec* const* codecs_;
+  int num_codecs_;
+  bool ec_enabled_;
+  bool ns_enabled_;
+  webrtc::EcModes ec_mode_;
+  webrtc::NsModes ns_mode_;
+  webrtc::AgcConfig agc_config_;
+  webrtc::VoiceEngineObserver* observer_;
+  int playout_fail_channel_;
+  int send_fail_channel_;
+  bool fail_start_recording_microphone_;
+  bool recording_microphone_;
+  webrtc::VoEMediaProcess* media_processor_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FAKEWEBRTCVOICEENGINE_H_
diff --git a/talk/session/phone/filemediaengine.cc b/talk/session/phone/filemediaengine.cc
index 2a7b8d8..6f2c5e1 100644
--- a/talk/session/phone/filemediaengine.cc
+++ b/talk/session/phone/filemediaengine.cc
@@ -34,6 +34,7 @@
 #include "talk/base/stream.h"
 #include "talk/session/phone/rtpdump.h"
 #include "talk/session/phone/rtputils.h"
+#include "talk/session/phone/streamparams.h"
 
 namespace cricket {
 
@@ -258,7 +259,8 @@
 FileVoiceChannel::FileVoiceChannel(
     talk_base::StreamInterface* input_file_stream,
     talk_base::StreamInterface* output_file_stream)
-    : rtp_sender_receiver_(new RtpSenderReceiver(this, input_file_stream,
+    : send_ssrc_(0),
+      rtp_sender_receiver_(new RtpSenderReceiver(this, input_file_stream,
                                                  output_file_stream)) {}
 
 FileVoiceChannel::~FileVoiceChannel() {}
@@ -272,8 +274,22 @@
   return rtp_sender_receiver_->SetSend(flag != SEND_NOTHING);
 }
 
-void FileVoiceChannel::SetSendSsrc(uint32 ssrc) {
-  rtp_sender_receiver_->SetSendSsrc(ssrc);
+bool FileVoiceChannel::AddSendStream(const StreamParams& sp) {
+  if (send_ssrc_ != 0 || sp.ssrcs.size() != 1) {
+    LOG(LS_ERROR) << "FileVoiceChannel only supports one send stream.";
+    return false;
+  }
+  send_ssrc_ = sp.ssrcs[0];
+  rtp_sender_receiver_->SetSendSsrc(send_ssrc_);
+  return true;
+}
+
+bool FileVoiceChannel::RemoveSendStream(uint32 ssrc) {
+  if (ssrc != send_ssrc_)
+    return false;
+  send_ssrc_ = 0;
+  rtp_sender_receiver_->SetSendSsrc(send_ssrc_);
+  return true;
 }
 
 void FileVoiceChannel::OnPacketReceived(talk_base::Buffer* packet) {
@@ -286,7 +302,8 @@
 FileVideoChannel::FileVideoChannel(
     talk_base::StreamInterface* input_file_stream,
     talk_base::StreamInterface* output_file_stream)
-    : rtp_sender_receiver_(new RtpSenderReceiver(this, input_file_stream,
+    : send_ssrc_(0),
+      rtp_sender_receiver_(new RtpSenderReceiver(this, input_file_stream,
                                                  output_file_stream)) {}
 
 FileVideoChannel::~FileVideoChannel() {}
@@ -300,8 +317,22 @@
   return rtp_sender_receiver_->SetSend(send);
 }
 
-void FileVideoChannel::SetSendSsrc(uint32 ssrc) {
-  rtp_sender_receiver_->SetSendSsrc(ssrc);
+bool FileVideoChannel::AddSendStream(const StreamParams& sp) {
+  if (send_ssrc_ != 0 || sp.ssrcs.size() != 1) {
+    LOG(LS_ERROR) << "FileVideoChannel only support one send stream.";
+    return false;
+  }
+  send_ssrc_ = sp.ssrcs[0];
+  rtp_sender_receiver_->SetSendSsrc(send_ssrc_);
+  return true;
+}
+
+bool FileVideoChannel::RemoveSendStream(uint32 ssrc) {
+  if (ssrc != send_ssrc_)
+    return false;
+  send_ssrc_ = 0;
+  rtp_sender_receiver_->SetSendSsrc(send_ssrc_);
+  return true;
 }
 
 void FileVideoChannel::OnPacketReceived(talk_base::Buffer* packet) {
diff --git a/talk/session/phone/filemediaengine.h b/talk/session/phone/filemediaengine.h
index b20e0f2..03d91ca 100644
--- a/talk/session/phone/filemediaengine.h
+++ b/talk/session/phone/filemediaengine.h
@@ -93,7 +93,13 @@
     return true;
   }
   virtual bool SetVideoCaptureDevice(const Device* cam_device) { return true; }
-  virtual bool GetOutputVolume(int* level) { *level = 0; return true; }
+  virtual bool SetVideoCapturer(VideoCapturer* /*capturer*/, uint32 /*ssrc*/) {
+    return true;
+  }
+  virtual bool GetOutputVolume(int* level) {
+    *level = 0;
+    return true;
+  }
   virtual bool SetOutputVolume(int level) { return true; }
   virtual int GetInputLevel() { return 0; }
   virtual bool SetLocalMonitor(bool enable) { return true; }
@@ -111,6 +117,23 @@
   virtual void SetVoiceLogging(int min_sev, const char* filter) {}
   virtual void SetVideoLogging(int min_sev, const char* filter) {}
 
+  virtual bool RegisterVideoProcessor(VideoProcessor* processor) {
+    return true;
+  }
+  virtual bool UnregisterVideoProcessor(VideoProcessor* processor) {
+    return true;
+  }
+  virtual bool RegisterVoiceProcessor(uint32 ssrc,
+                                      VoiceProcessor* processor,
+                                      MediaProcessorDirection direction) {
+    return true;
+  }
+  virtual bool UnregisterVoiceProcessor(uint32 ssrc,
+                                        VoiceProcessor* processor,
+                                        MediaProcessorDirection direction) {
+    return true;
+  }
+
  private:
   std::string voice_input_filename_;
   std::string voice_output_filename_;
@@ -145,8 +168,6 @@
   }
   virtual bool SetPlayout(bool playout) { return true; }
   virtual bool SetSend(SendFlags flag);
-  virtual bool AddStream(uint32 ssrc) { return true; }
-  virtual bool RemoveStream(uint32 ssrc) { return true; }
   virtual bool GetActiveStreams(AudioInfo::StreamList* actives) { return true; }
   virtual int GetOutputLevel() { return 0; }
   virtual bool SetOutputScaling(uint32 ssrc, double left, double right) {
@@ -165,13 +186,16 @@
   // Implement pure virtual methods of MediaChannel.
   virtual void OnPacketReceived(talk_base::Buffer* packet);
   virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
-  virtual void SetSendSsrc(uint32 ssrc);
-  virtual bool SetRtcpCName(const std::string& cname) { return true; }
+  virtual bool AddSendStream(const StreamParams& sp);
+  virtual bool RemoveSendStream(uint32 ssrc);
+  virtual bool AddRecvStream(const StreamParams& sp) { return true; }
+  virtual bool RemoveRecvStream(uint32 ssrc) { return true; }
   virtual bool Mute(bool on) { return false; }
   virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
   virtual bool SetOptions(int options) { return true; }
 
  private:
+  uint32 send_ssrc_;
   talk_base::scoped_ptr<RtpSenderReceiver> rtp_sender_receiver_;
   DISALLOW_COPY_AND_ASSIGN(FileVoiceChannel);
 };
@@ -187,6 +211,9 @@
     return true;
   }
   virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs);
+  virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format) {
+    return true;
+  }
   virtual bool SetRecvRtpHeaderExtensions(
       const std::vector<RtpHeaderExtension>& extensions) {
     return true;
@@ -197,11 +224,13 @@
   }
   virtual bool SetRender(bool render) { return true; }
   virtual bool SetSend(bool send);
-  virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc) { return true; }
-  virtual bool RemoveStream(uint32 ssrc) { return true; }
   virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
     return true;
   }
+  virtual bool AddScreencast(uint32 ssrc, const ScreencastId& id) {
+    return true;
+  }
+  virtual bool RemoveScreencast(uint32 ssrc) { return true; }
   virtual bool GetStats(VideoMediaInfo* info) { return true; }
   virtual bool SendIntraFrame() { return false; }
   virtual bool RequestIntraFrame() { return false; }
@@ -209,13 +238,16 @@
   // Implement pure virtual methods of MediaChannel.
   virtual void OnPacketReceived(talk_base::Buffer* packet);
   virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
-  virtual void SetSendSsrc(uint32 ssrc);
-  virtual bool SetRtcpCName(const std::string& cname) { return true; }
+  virtual bool AddSendStream(const StreamParams& sp);
+  virtual bool RemoveSendStream(uint32 ssrc);
+  virtual bool AddRecvStream(const StreamParams& sp) { return true; }
+  virtual bool RemoveRecvStream(uint32 ssrc) { return true; }
   virtual bool Mute(bool on) { return false; }
   virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
   virtual bool SetOptions(int options) { return true; }
 
  private:
+  uint32 send_ssrc_;
   talk_base::scoped_ptr<RtpSenderReceiver> rtp_sender_receiver_;
   DISALLOW_COPY_AND_ASSIGN(FileVideoChannel);
 };
diff --git a/talk/session/phone/filemediaengine_unittest.cc b/talk/session/phone/filemediaengine_unittest.cc
new file mode 100644
index 0000000..c915251
--- /dev/null
+++ b/talk/session/phone/filemediaengine_unittest.cc
@@ -0,0 +1,463 @@
+/*
+ * 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 <set>
+
+#include "talk/base/buffer.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/session/phone/filemediaengine.h"
+#include "talk/session/phone/rtpdump.h"
+#include "talk/session/phone/streamparams.h"
+#include "talk/session/phone/testutils.h"
+
+namespace cricket {
+
+static const int kWaitTimeMs = 100;
+static const std::string kFakeFileName = "foobar";
+
+//////////////////////////////////////////////////////////////////////////////
+// Media channel sends RTP packets via NetworkInterface. Rather than sending
+// packets to the network, FileNetworkInterface writes packets to a stream and
+// feeds packets back to the channel via OnPacketReceived.
+//////////////////////////////////////////////////////////////////////////////
+class FileNetworkInterface : public MediaChannel::NetworkInterface {
+ public:
+  FileNetworkInterface(talk_base::StreamInterface* output, MediaChannel* ch)
+      : media_channel_(ch),
+        num_sent_packets_(0) {
+    if (output) {
+      dump_writer_.reset(new RtpDumpWriter(output));
+    }
+  }
+
+  // Implement pure virtual methods of NetworkInterface.
+  virtual bool SendPacket(talk_base::Buffer* packet) {
+    if (!packet) return false;
+
+    if (media_channel_) {
+      media_channel_->OnPacketReceived(packet);
+    }
+    if (dump_writer_.get() &&
+        talk_base::SR_SUCCESS != dump_writer_->WriteRtpPacket(
+            packet->data(), packet->length())) {
+      return false;
+    }
+
+    ++num_sent_packets_;
+    return true;
+  }
+
+  virtual bool SendRtcp(talk_base::Buffer* packet) { return false; }
+  virtual int SetOption(MediaChannel::NetworkInterface::SocketType type,
+      talk_base::Socket::Option opt, int option) {
+    return 0;
+  }
+
+  size_t num_sent_packets() const { return num_sent_packets_; }
+
+ private:
+  MediaChannel* media_channel_;
+  talk_base::scoped_ptr<RtpDumpWriter> dump_writer_;
+  size_t num_sent_packets_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileNetworkInterface);
+};
+
+class FileMediaEngineTest : public testing::Test {
+ public:
+  virtual void SetUp() {
+    setup_ok_ = true;
+    setup_ok_ &= GetTempFilename(&voice_input_filename_);
+    setup_ok_ &= GetTempFilename(&voice_output_filename_);
+    setup_ok_ &= GetTempFilename(&video_input_filename_);
+    setup_ok_ &= GetTempFilename(&video_output_filename_);
+  }
+  virtual void TearDown() {
+    // Force to close the dump files, if opened.
+    voice_channel_.reset();
+    video_channel_.reset();
+
+    DeleteTempFile(voice_input_filename_);
+    DeleteTempFile(voice_output_filename_);
+    DeleteTempFile(video_input_filename_);
+    DeleteTempFile(video_output_filename_);
+  }
+
+ protected:
+  bool CreateEngineAndChannels(const std::string& voice_in,
+                               const std::string& voice_out,
+                               const std::string& video_in,
+                               const std::string& video_out,
+                               size_t ssrc_count) {
+    // Force to close the dump files, if opened.
+    voice_channel_.reset();
+    video_channel_.reset();
+
+    bool ret = setup_ok_;
+    if (!voice_in.empty()) {
+      ret &= WriteTestPacketsToFile(voice_in, ssrc_count);
+    }
+    if (!video_in.empty()) {
+      ret &= WriteTestPacketsToFile(video_in, ssrc_count);
+    }
+
+    engine_.reset(new FileMediaEngine);
+    engine_->set_voice_input_filename(voice_in);
+    engine_->set_voice_output_filename(voice_out);
+    engine_->set_video_input_filename(video_in);
+    engine_->set_video_output_filename(video_out);
+
+    voice_channel_.reset(engine_->CreateChannel());
+    video_channel_.reset(engine_->CreateVideoChannel(NULL));
+
+    return ret;
+  }
+
+  bool GetTempFilename(std::string* filename) {
+    talk_base::Pathname temp_path;
+    if (!talk_base::Filesystem::GetTemporaryFolder(temp_path, true, NULL)) {
+      return false;
+    }
+    temp_path.SetPathname(
+        talk_base::Filesystem::TempFilename(temp_path, "fme-test-"));
+
+    if (filename) {
+      *filename = temp_path.pathname();
+    }
+    return true;
+  }
+
+  bool WriteTestPacketsToFile(const std::string& filename, size_t ssrc_count) {
+    talk_base::scoped_ptr<talk_base::StreamInterface> stream(
+        talk_base::Filesystem::OpenFile(talk_base::Pathname(filename), "wb"));
+    bool ret = (NULL != stream.get());
+    RtpDumpWriter writer(stream.get());
+
+    for (size_t i = 0; i < ssrc_count; ++i) {
+      ret &= RtpTestUtility::WriteTestPackets(
+          RtpTestUtility::GetTestPacketCount(), false,
+          RtpTestUtility::kDefaultSsrc + i, &writer);
+    }
+    return ret;
+  }
+
+  void DeleteTempFile(std::string filename) {
+    talk_base::Pathname pathname(filename);
+    if (talk_base::Filesystem::IsFile(talk_base::Pathname(pathname))) {
+      talk_base::Filesystem::DeleteFile(pathname);
+    }
+  }
+
+  bool GetSsrcAndPacketCounts(talk_base::StreamInterface* stream,
+                              size_t* ssrc_count, size_t* packet_count) {
+    talk_base::scoped_ptr<RtpDumpReader> reader(new RtpDumpReader(stream));
+    size_t count = 0;
+    RtpDumpPacket packet;
+    std::set<uint32> ssrcs;
+    while (talk_base::SR_SUCCESS == reader->ReadPacket(&packet)) {
+      count++;
+      uint32 ssrc;
+      if (!packet.GetRtpSsrc(&ssrc)) {
+        return false;
+      }
+      ssrcs.insert(ssrc);
+    }
+    if (ssrc_count) {
+      *ssrc_count = ssrcs.size();
+    }
+    if (packet_count) {
+      *packet_count = count;
+    }
+    return true;
+  }
+
+  static const uint32 kWaitTimeout = 3000;
+  bool setup_ok_;
+  std::string voice_input_filename_;
+  std::string voice_output_filename_;
+  std::string video_input_filename_;
+  std::string video_output_filename_;
+  talk_base::scoped_ptr<FileMediaEngine> engine_;
+  talk_base::scoped_ptr<VoiceMediaChannel> voice_channel_;
+  talk_base::scoped_ptr<VideoMediaChannel> video_channel_;
+};
+
+TEST_F(FileMediaEngineTest, TestDefaultImplementation) {
+  EXPECT_TRUE(CreateEngineAndChannels("", "", "", "", 1));
+  EXPECT_TRUE(engine_->Init());
+  EXPECT_EQ(0, engine_->GetCapabilities());
+  EXPECT_TRUE(NULL == voice_channel_.get());
+  EXPECT_TRUE(NULL == video_channel_.get());
+  EXPECT_TRUE(NULL == engine_->CreateSoundclip());
+  EXPECT_TRUE(engine_->SetAudioOptions(0));
+  EXPECT_TRUE(engine_->SetVideoOptions(0));
+  VideoEncoderConfig video_encoder_config;
+  EXPECT_TRUE(engine_->SetDefaultVideoEncoderConfig(video_encoder_config));
+  EXPECT_TRUE(engine_->SetSoundDevices(NULL, NULL));
+  EXPECT_TRUE(engine_->SetVideoCaptureDevice(NULL));
+  EXPECT_TRUE(engine_->SetOutputVolume(0));
+  EXPECT_EQ(0, engine_->GetInputLevel());
+  EXPECT_TRUE(engine_->SetLocalMonitor(true));
+  EXPECT_TRUE(engine_->SetLocalRenderer(NULL));
+  EXPECT_EQ(CR_SUCCESS, engine_->SetVideoCapture(true));
+  EXPECT_EQ(0U, engine_->audio_codecs().size());
+  EXPECT_EQ(0U, engine_->video_codecs().size());
+  AudioCodec voice_codec;
+  EXPECT_TRUE(engine_->FindAudioCodec(voice_codec));
+  VideoCodec video_codec;
+  EXPECT_TRUE(engine_->FindVideoCodec(video_codec));
+  engine_->Terminate();
+}
+
+// Test that when file path is not pointing to a valid stream file, the channel
+// creation function should fail and return NULL.
+TEST_F(FileMediaEngineTest, TestBadFilePath) {
+  engine_.reset(new FileMediaEngine);
+  engine_->set_voice_input_filename(kFakeFileName);
+  engine_->set_video_input_filename(kFakeFileName);
+  EXPECT_TRUE(engine_->CreateChannel() == NULL);
+  EXPECT_TRUE(engine_->CreateVideoChannel(NULL) == NULL);
+}
+
+TEST_F(FileMediaEngineTest, TestCodecs) {
+  EXPECT_TRUE(CreateEngineAndChannels("", "", "", "", 1));
+  std::vector<AudioCodec> voice_codecs = engine_->audio_codecs();
+  std::vector<VideoCodec> video_codecs = engine_->video_codecs();
+  EXPECT_EQ(0U, voice_codecs.size());
+  EXPECT_EQ(0U, video_codecs.size());
+
+  AudioCodec voice_codec(103, "ISAC", 16000, 0, 1, 0);
+  voice_codecs.push_back(voice_codec);
+  engine_->set_voice_codecs(voice_codecs);
+  voice_codecs = engine_->audio_codecs();
+  ASSERT_EQ(1U, voice_codecs.size());
+  EXPECT_EQ(voice_codec, voice_codecs[0]);
+
+  VideoCodec video_codec(96, "H264-SVC", 320, 240, 30, 0);
+  video_codecs.push_back(video_codec);
+  engine_->set_video_codecs(video_codecs);
+  video_codecs = engine_->video_codecs();
+  ASSERT_EQ(1U, video_codecs.size());
+  EXPECT_EQ(video_codec, video_codecs[0]);
+}
+
+// Test that the capabilities and channel creation of the Filemedia engine
+// depend on the stream parameters passed to its constructor.
+TEST_F(FileMediaEngineTest, TestGetCapabilities) {
+  EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_, "", "", "", 1));
+  EXPECT_EQ(AUDIO_SEND, engine_->GetCapabilities());
+  EXPECT_TRUE(NULL != voice_channel_.get());
+  EXPECT_TRUE(NULL == video_channel_.get());
+
+  EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+                                      voice_output_filename_, "", "", 1));
+  EXPECT_EQ(AUDIO_SEND | AUDIO_RECV, engine_->GetCapabilities());
+  EXPECT_TRUE(NULL != voice_channel_.get());
+  EXPECT_TRUE(NULL == video_channel_.get());
+
+  EXPECT_TRUE(CreateEngineAndChannels("", "", video_input_filename_, "", 1));
+  EXPECT_EQ(VIDEO_SEND, engine_->GetCapabilities());
+  EXPECT_TRUE(NULL == voice_channel_.get());
+  EXPECT_TRUE(NULL != video_channel_.get());
+
+  EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+                                      voice_output_filename_,
+                                      video_input_filename_,
+                                      video_output_filename_,
+                                      1));
+  EXPECT_EQ(AUDIO_SEND | AUDIO_RECV | VIDEO_SEND | VIDEO_RECV,
+            engine_->GetCapabilities());
+  EXPECT_TRUE(NULL != voice_channel_.get());
+  EXPECT_TRUE(NULL != video_channel_.get());
+}
+
+// FileVideoChannel is the same as FileVoiceChannel in terms of receiving and
+// sending the RTP packets. We therefore test only FileVoiceChannel.
+
+// Test that SetSend() controls whether a voice channel sends RTP packets.
+TEST_F(FileMediaEngineTest, TestVoiceChannelSetSend) {
+  EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+                                      voice_output_filename_, "", "", 1));
+  EXPECT_TRUE(NULL != voice_channel_.get());
+  talk_base::MemoryStream net_dump;
+  FileNetworkInterface net_interface(&net_dump, voice_channel_.get());
+  voice_channel_->SetInterface(&net_interface);
+
+  // The channel is not sending yet.
+  talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+  EXPECT_EQ(0U, net_interface.num_sent_packets());
+
+  // The channel starts sending.
+  voice_channel_->SetSend(SEND_MICROPHONE);
+  EXPECT_TRUE_WAIT(net_interface.num_sent_packets() >= 1U, kWaitTimeout);
+
+  // The channel stops sending.
+  voice_channel_->SetSend(SEND_NOTHING);
+  // Wait until packets are all delivered.
+  talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+  size_t old_number = net_interface.num_sent_packets();
+  talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+  EXPECT_EQ(old_number, net_interface.num_sent_packets());
+
+  // The channel starts sending again.
+  voice_channel_->SetSend(SEND_MICROPHONE);
+  EXPECT_TRUE_WAIT(net_interface.num_sent_packets() > old_number, kWaitTimeout);
+
+  // When the function exits, the net_interface object is released. The sender
+  // thread may call net_interface to send packets, which results in a segment
+  // fault. We hence stop sending and wait until all packets are delivered
+  // before we exit this function.
+  voice_channel_->SetSend(SEND_NOTHING);
+  talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+}
+
+// Test the sender thread of the channel. The sender sends RTP packets
+// continuously with proper sequence number, timestamp, and payload.
+TEST_F(FileMediaEngineTest, TestVoiceChannelSenderThread) {
+  EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+                                      voice_output_filename_, "", "", 1));
+  EXPECT_TRUE(NULL != voice_channel_.get());
+  talk_base::MemoryStream net_dump;
+  FileNetworkInterface net_interface(&net_dump, voice_channel_.get());
+  voice_channel_->SetInterface(&net_interface);
+
+  voice_channel_->SetSend(SEND_MICROPHONE);
+  // Wait until the number of sent packets is no less than 2 * kPacketNumber.
+  EXPECT_TRUE_WAIT(
+      net_interface.num_sent_packets() >=
+          2 * RtpTestUtility::GetTestPacketCount(),
+      kWaitTimeout);
+  voice_channel_->SetSend(SEND_NOTHING);
+  // Wait until packets are all delivered.
+  talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+  EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+      2 * RtpTestUtility::GetTestPacketCount(), &net_dump,
+      RtpTestUtility::kDefaultSsrc));
+
+  // Each sent packet is dumped to net_dump and is also feed to the channel
+  // via OnPacketReceived, which in turn writes the packets into voice_output_.
+  // We next verify the packets in voice_output_.
+  voice_channel_.reset();  // Force to close the files.
+  talk_base::scoped_ptr<talk_base::StreamInterface> voice_output_;
+  voice_output_.reset(talk_base::Filesystem::OpenFile(
+      talk_base::Pathname(voice_output_filename_), "rb"));
+  EXPECT_TRUE(voice_output_.get() != NULL);
+  EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+      2 * RtpTestUtility::GetTestPacketCount(), voice_output_.get(),
+      RtpTestUtility::kDefaultSsrc));
+}
+
+// Test that we can specify the ssrc for outgoing RTP packets.
+TEST_F(FileMediaEngineTest, TestVoiceChannelSendSsrc) {
+  EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+                                      voice_output_filename_, "", "", 1));
+  EXPECT_TRUE(NULL != voice_channel_.get());
+  const uint32 send_ssrc = RtpTestUtility::kDefaultSsrc + 1;
+  voice_channel_->AddSendStream(StreamParams::CreateLegacy(send_ssrc));
+
+  talk_base::MemoryStream net_dump;
+  FileNetworkInterface net_interface(&net_dump, voice_channel_.get());
+  voice_channel_->SetInterface(&net_interface);
+
+  voice_channel_->SetSend(SEND_MICROPHONE);
+  // Wait until the number of sent packets is no less than 2 * kPacketNumber.
+  EXPECT_TRUE_WAIT(
+      net_interface.num_sent_packets() >=
+          2 * RtpTestUtility::GetTestPacketCount(),
+      kWaitTimeout);
+  voice_channel_->SetSend(SEND_NOTHING);
+  // Wait until packets are all delivered.
+  talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+  EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+      2 * RtpTestUtility::GetTestPacketCount(), &net_dump, send_ssrc));
+
+  // Each sent packet is dumped to net_dump and is also feed to the channel
+  // via OnPacketReceived, which in turn writes the packets into voice_output_.
+  // We next verify the packets in voice_output_.
+  voice_channel_.reset();  // Force to close the files.
+  talk_base::scoped_ptr<talk_base::StreamInterface> voice_output_;
+  voice_output_.reset(talk_base::Filesystem::OpenFile(
+      talk_base::Pathname(voice_output_filename_), "rb"));
+  EXPECT_TRUE(voice_output_.get() != NULL);
+  EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+      2 * RtpTestUtility::GetTestPacketCount(), voice_output_.get(),
+      send_ssrc));
+}
+
+// Test the sender thread of the channel, where the input rtpdump has two SSRCs.
+TEST_F(FileMediaEngineTest, TestVoiceChannelSenderThreadTwoSsrcs) {
+  EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+                                      voice_output_filename_, "", "", 2));
+  // Verify that voice_input_filename_ contains 2 *
+  // RtpTestUtility::GetTestPacketCount() packets
+  // with different SSRCs.
+  talk_base::scoped_ptr<talk_base::StreamInterface> input_stream(
+      talk_base::Filesystem::OpenFile(
+          talk_base::Pathname(voice_input_filename_), "rb"));
+  ASSERT_TRUE(NULL != input_stream.get());
+  size_t ssrc_count;
+  size_t packet_count;
+  EXPECT_TRUE(GetSsrcAndPacketCounts(input_stream.get(), &ssrc_count,
+                                     &packet_count));
+  EXPECT_EQ(2U, ssrc_count);
+  EXPECT_EQ(2 * RtpTestUtility::GetTestPacketCount(), packet_count);
+  input_stream.reset();
+
+  // Send 2 * RtpTestUtility::GetTestPacketCount() packets and verify that all
+  // these packets have the same SSRCs (that is, the packets with different
+  // SSRCs are skipped by the filemediaengine).
+  EXPECT_TRUE(NULL != voice_channel_.get());
+  talk_base::MemoryStream net_dump;
+  FileNetworkInterface net_interface(&net_dump, voice_channel_.get());
+  voice_channel_->SetInterface(&net_interface);
+  voice_channel_->SetSend(SEND_MICROPHONE);
+  EXPECT_TRUE_WAIT(
+      net_interface.num_sent_packets() >=
+          2 * RtpTestUtility::GetTestPacketCount(),
+      kWaitTimeout);
+  voice_channel_->SetSend(SEND_NOTHING);
+  // Wait until packets are all delivered.
+  talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+  net_dump.Rewind();
+  EXPECT_TRUE(GetSsrcAndPacketCounts(&net_dump, &ssrc_count, &packet_count));
+  EXPECT_EQ(1U, ssrc_count);
+  EXPECT_GE(packet_count, 2 * RtpTestUtility::GetTestPacketCount());
+}
+
+// Test SendIntraFrame() and RequestIntraFrame() of video channel.
+TEST_F(FileMediaEngineTest, TestVideoChannelIntraFrame) {
+  EXPECT_TRUE(CreateEngineAndChannels("", "", video_input_filename_,
+                                      video_output_filename_, 1));
+  EXPECT_TRUE(NULL != video_channel_.get());
+  EXPECT_FALSE(video_channel_->SendIntraFrame());
+  EXPECT_FALSE(video_channel_->RequestIntraFrame());
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/filevideocapturer.cc b/talk/session/phone/filevideocapturer.cc
new file mode 100644
index 0000000..00272c5
--- /dev/null
+++ b/talk/session/phone/filevideocapturer.cc
@@ -0,0 +1,350 @@
+// 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.
+//
+// Implementation of VideoRecorder and FileVideoCapturer.
+
+#include "talk/session/phone/filevideocapturer.h"
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+
+namespace cricket {
+
+/////////////////////////////////////////////////////////////////////
+// Implementation of class VideoRecorder
+/////////////////////////////////////////////////////////////////////
+bool VideoRecorder::Start(const std::string& filename, bool write_header) {
+  Stop();
+  write_header_ = write_header;
+  int err;
+  if (!video_file_.Open(filename, "wb", &err)) {
+    LOG(LS_ERROR) << "Unable to open file " << filename << " err=" << err;
+    return false;
+  }
+  return true;
+}
+
+void VideoRecorder::Stop() {
+  video_file_.Close();
+}
+
+bool VideoRecorder::RecordFrame(const CapturedFrame& frame) {
+  if (talk_base::SS_CLOSED == video_file_.GetState()) {
+    LOG(LS_ERROR) << "File not opened yet";
+    return false;
+  }
+
+  uint32 size = 0;
+  if (!frame.GetDataSize(&size)) {
+    LOG(LS_ERROR) << "Unable to calculate the data size of the frame";
+    return false;
+  }
+
+  if (write_header_) {
+    // Convert the frame header to bytebuffer.
+    talk_base::ByteBuffer buffer;
+    buffer.WriteUInt32(frame.width);
+    buffer.WriteUInt32(frame.height);
+    buffer.WriteUInt32(frame.fourcc);
+    buffer.WriteUInt32(frame.pixel_width);
+    buffer.WriteUInt32(frame.pixel_height);
+    buffer.WriteUInt64(frame.elapsed_time);
+    buffer.WriteUInt64(frame.time_stamp);
+    buffer.WriteUInt32(size);
+
+    // Write the bytebuffer to file.
+    if (talk_base::SR_SUCCESS != video_file_.Write(buffer.Data(),
+                                                   buffer.Length(),
+                                                   NULL,
+                                                   NULL)) {
+      LOG(LS_ERROR) << "Failed to write frame header";
+      return false;
+    }
+  }
+  // Write the frame data to file.
+  if (talk_base::SR_SUCCESS != video_file_.Write(frame.data,
+                                                 size,
+                                                 NULL,
+                                                 NULL)) {
+    LOG(LS_ERROR) << "Failed to write frame data";
+    return false;
+  }
+
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////
+// Definition of private class FileReadThread that periodically reads
+// frames from a file.
+///////////////////////////////////////////////////////////////////////
+class FileVideoCapturer::FileReadThread
+    : public talk_base::Thread, public talk_base::MessageHandler {
+ public:
+  explicit FileReadThread(FileVideoCapturer* capturer)
+      : capturer_(capturer),
+        finished_(false) {
+  }
+
+  // Override virtual method of parent Thread. Context: Worker Thread.
+  virtual void Run() {
+    // Read the first frame and start the message pump. The pump runs until
+    // Stop() is called externally or Quit() is called by OnMessage().
+    int waiting_time_ms = 0;
+    if (capturer_ && capturer_->ReadFrame(true, &waiting_time_ms)) {
+      PostDelayed(waiting_time_ms, this);
+      Thread::Run();
+    }
+    finished_ = true;
+  }
+
+  // Override virtual method of parent MessageHandler. Context: Worker Thread.
+  virtual void OnMessage(talk_base::Message* /*pmsg*/) {
+    int waiting_time_ms = 0;
+    if (capturer_ && capturer_->ReadFrame(false, &waiting_time_ms)) {
+      PostDelayed(waiting_time_ms, this);
+    } else {
+      Quit();
+    }
+  }
+
+  // Check if Run() is finished.
+  bool Finished() const { return finished_; }
+
+ private:
+  FileVideoCapturer* capturer_;
+  bool finished_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileReadThread);
+};
+
+/////////////////////////////////////////////////////////////////////
+// Implementation of class FileVideoCapturer
+/////////////////////////////////////////////////////////////////////
+static const int64 kNumNanoSecsPerMilliSec = 1000000;
+
+FileVideoCapturer::FileVideoCapturer()
+    : frame_buffer_size_(0),
+      file_read_thread_(NULL),
+      repeat_(0),
+      start_time_ns_(0),
+      last_frame_timestamp_ns_(0),
+      ignore_framerate_(false) {
+}
+
+FileVideoCapturer::~FileVideoCapturer() {
+  Stop();
+  delete[] static_cast<char*> (captured_frame_.data);
+}
+
+bool FileVideoCapturer::Init(const std::string& filename) {
+  if (IsRunning()) {
+    LOG(LS_ERROR) << "The file video capturer is already running";
+    return false;
+  }
+  // Open the file.
+  int err;
+  if (!video_file_.Open(filename, "rb", &err)) {
+    LOG(LS_ERROR) << "Unable to open the file " << filename << " err=" << err;
+    return false;
+  }
+  // Read the first frame's header to determine the supported format.
+  CapturedFrame frame;
+  if (talk_base::SR_SUCCESS != ReadFrameHeader(&frame)) {
+    LOG(LS_ERROR) << "Failed to read the first frame header";
+    video_file_.Close();
+    return false;
+  }
+  // Seek back to the start of the file.
+  if (!video_file_.SetPosition(0)) {
+    LOG(LS_ERROR) << "Failed to seek back to beginning of the file";
+    video_file_.Close();
+    return false;
+  }
+
+  // Enumerate the supported formats. We have only one supported format. We set
+  // the frame interval to kMinimumInterval here. In Start(), if the capture
+  // format's interval is greater than kMinimumInterval, we use the interval;
+  // otherwise, we use the timestamp in the file to control the interval.
+  VideoFormat format(frame.width, frame.height, VideoFormat::kMinimumInterval,
+                     frame.fourcc);
+  std::vector<VideoFormat> supported;
+  supported.push_back(format);
+
+  SetId(filename);
+  SetSupportedFormats(supported);
+  return true;
+}
+
+CaptureResult FileVideoCapturer::Start(const VideoFormat& capture_format) {
+  if (IsRunning()) {
+    LOG(LS_ERROR) << "The file video capturer is already running";
+    return CR_FAILURE;
+  }
+
+  if (talk_base::SS_CLOSED == video_file_.GetState()) {
+    LOG(LS_ERROR) << "File not opened yet";
+    return CR_NO_DEVICE;
+  } else if (!video_file_.SetPosition(0)) {
+    LOG(LS_ERROR) << "Failed to seek back to beginning of the file";
+    return CR_FAILURE;
+  }
+
+  SetCaptureFormat(&capture_format);
+  // Create a thread to read the file.
+  file_read_thread_ = new FileReadThread(this);
+  bool ret = file_read_thread_->Start();
+  start_time_ns_ = kNumNanoSecsPerMilliSec *
+      static_cast<int64>(talk_base::Time());
+  if (ret) {
+    LOG(LS_INFO) << "File video capturer '" << GetId() << "' started";
+    return CR_SUCCESS;
+  } else {
+    LOG(LS_ERROR) << "File video capturer '" << GetId() << "' failed to start";
+    return CR_FAILURE;
+  }
+}
+
+bool FileVideoCapturer::IsRunning() {
+  return file_read_thread_ && !file_read_thread_->Finished();
+}
+
+void FileVideoCapturer::Stop() {
+  if (file_read_thread_) {
+    file_read_thread_->Stop();
+    file_read_thread_ = NULL;
+    LOG(LS_INFO) << "File video capturer '" << GetId() << "' stopped";
+  }
+  SetCaptureFormat(NULL);
+}
+
+bool FileVideoCapturer::GetPreferredFourccs(std::vector<uint32>* fourccs) {
+  if (!fourccs) {
+    return false;
+  }
+
+  fourccs->push_back(GetSupportedFormats()->at(0).fourcc);
+  return true;
+}
+
+talk_base::StreamResult FileVideoCapturer::ReadFrameHeader(
+    CapturedFrame* frame) {
+  // We first read kFrameHeaderSize bytes from the file stream to a memory
+  // buffer, then construct a bytebuffer from the memory buffer, and finally
+  // read the frame header from the bytebuffer.
+  char header[CapturedFrame::kFrameHeaderSize];
+  talk_base::StreamResult sr;
+  sr = video_file_.Read(header,
+                        CapturedFrame::kFrameHeaderSize,
+                        NULL,
+                        NULL);
+  if (talk_base::SR_SUCCESS == sr) {
+    talk_base::ByteBuffer buffer(header, CapturedFrame::kFrameHeaderSize);
+    buffer.ReadUInt32(reinterpret_cast<uint32*>(&frame->width));
+    buffer.ReadUInt32(reinterpret_cast<uint32*>(&frame->height));
+    buffer.ReadUInt32(&frame->fourcc);
+    buffer.ReadUInt32(&frame->pixel_width);
+    buffer.ReadUInt32(&frame->pixel_height);
+    buffer.ReadUInt64(reinterpret_cast<uint64*>(&frame->elapsed_time));
+    buffer.ReadUInt64(reinterpret_cast<uint64*>(&frame->time_stamp));
+    buffer.ReadUInt32(&frame->data_size);
+  }
+
+  return sr;
+}
+
+// Executed in the context of FileReadThread.
+bool FileVideoCapturer::ReadFrame(bool first_frame, int* wait_time_ms) {
+  uint32 start_read_time_ms = talk_base::Time();
+
+  // 1. Signal the previously read frame to downstream.
+  if (!first_frame) {
+    captured_frame_.time_stamp = kNumNanoSecsPerMilliSec *
+        static_cast<int64>(start_read_time_ms);
+    captured_frame_.elapsed_time = captured_frame_.time_stamp - start_time_ns_;
+    SignalFrameCaptured(this, &captured_frame_);
+  }
+
+  // 2. Read the next frame.
+  if (talk_base::SS_CLOSED == video_file_.GetState()) {
+    LOG(LS_ERROR) << "File not opened yet";
+    return false;
+  }
+  // 2.1 Read the frame header.
+  talk_base::StreamResult result = ReadFrameHeader(&captured_frame_);
+  if (talk_base::SR_EOS == result) {  // Loop back if repeat.
+    if (repeat_ != talk_base::kForever) {
+      if (repeat_ > 0) {
+        --repeat_;
+      } else {
+        return false;
+      }
+    }
+
+    if (video_file_.SetPosition(0)) {
+      result = ReadFrameHeader(&captured_frame_);
+    }
+  }
+  if (talk_base::SR_SUCCESS != result) {
+    LOG(LS_ERROR) << "Failed to read the frame header";
+    return false;
+  }
+  // 2.2 Reallocate memory for the frame data if necessary.
+  if (frame_buffer_size_ < captured_frame_.data_size) {
+    frame_buffer_size_ = captured_frame_.data_size;
+    delete[] static_cast<char*> (captured_frame_.data);
+    captured_frame_.data = new char[frame_buffer_size_];
+  }
+  // 2.3 Read the frame adata.
+  if (talk_base::SR_SUCCESS != video_file_.Read(captured_frame_.data,
+                                                captured_frame_.data_size,
+                                                NULL, NULL)) {
+    LOG(LS_ERROR) << "Failed to read frame data";
+    return false;
+  }
+
+  // 3. Decide how long to wait for the next frame.
+  *wait_time_ms = 0;
+
+  // If the capture format's interval is not kMinimumInterval, we use it to
+  // control the rate; otherwise, we use the timestamp in the file to control
+  // the rate.
+  if (!first_frame && !ignore_framerate_) {
+    int64 interval_ns =
+        GetCaptureFormat()->interval > VideoFormat::kMinimumInterval ?
+        GetCaptureFormat()->interval :
+        captured_frame_.time_stamp - last_frame_timestamp_ns_;
+    int interval_ms = static_cast<int>(interval_ns / kNumNanoSecsPerMilliSec);
+    interval_ms -= talk_base::Time() - start_read_time_ms;
+    if (interval_ms > 0) {
+      *wait_time_ms = interval_ms;
+    }
+  }
+  // Keep the original timestamp read from the file.
+  last_frame_timestamp_ns_ = captured_frame_.time_stamp;
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/filevideocapturer.h b/talk/session/phone/filevideocapturer.h
new file mode 100644
index 0000000..6423c02
--- /dev/null
+++ b/talk/session/phone/filevideocapturer.h
@@ -0,0 +1,135 @@
+// libjingle
+// Copyright 2004--2005, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This file contains two classes, VideoRecorder and FileVideoCapturer.
+// VideoRecorder records the captured frames into a file. The file stores a
+// sequence of captured frames; each frame has a header defined in struct
+// CapturedFrame, followed by the frame data.
+//
+// FileVideoCapturer, a subclass of VideoCapturer, is a simulated video capturer
+// that periodically reads images from a previously recorded file.
+
+#ifndef TALK_SESSION_PHONE_FILEVIDEOCAPTURER_H_
+#define TALK_SESSION_PHONE_FILEVIDEOCAPTURER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/stream.h"
+#include "talk/session/phone/videocapturer.h"
+
+namespace talk_base {
+class FileStream;
+}
+
+namespace cricket {
+
+// Utility class to record the frames captured by a video capturer into a file.
+class VideoRecorder {
+ public:
+  VideoRecorder() {}
+  ~VideoRecorder() { Stop(); }
+
+  // Start the recorder by opening the specified file. Return true if the file
+  // is opened successfully. write_header should normally be true; false means
+  // write raw frame pixel data to file without any headers.
+  bool Start(const std::string& filename, bool write_header);
+  // Stop the recorder by closing the file.
+  void Stop();
+  // Record a video frame to the file. Return true if the frame is written to
+  // the file successfully. This method needs to be called after Start() and
+  // before Stop().
+  bool RecordFrame(const CapturedFrame& frame);
+
+ private:
+  talk_base::FileStream video_file_;
+  bool write_header_;
+
+  DISALLOW_COPY_AND_ASSIGN(VideoRecorder);
+};
+
+// Simulated video capturer that periodically reads frames from a file.
+class FileVideoCapturer : public VideoCapturer {
+ public:
+  FileVideoCapturer();
+  virtual ~FileVideoCapturer();
+
+  // Set how many times to repeat reading the file. Repeat forever if the
+  // parameter is talk_base::kForever(-1); no repeat if the parameter is 0 or
+  // less than -1.
+  void set_repeat(int repeat) { repeat_ = repeat; }
+
+  // If ignore_framerate is true, file is read as quickly as possible. If
+  // false, read rate is controlled by the timestamps in the video file
+  // (thus simulating camera capture). Default value set to false.
+  void set_ignore_framerate(bool ignore_framerate) {
+    ignore_framerate_ = ignore_framerate;
+  }
+
+  bool Init(const std::string& filename);
+
+  // Override virtual methods of parent class VideoCapturer.
+  virtual CaptureResult Start(const VideoFormat& capture_format);
+  virtual void Stop();
+  virtual bool IsRunning();
+
+ protected:
+  // Override virtual methods of parent class VideoCapturer.
+  virtual bool GetPreferredFourccs(std::vector<uint32>* fourccs);
+
+  // Read the frame header from the file stream, video_file_.
+  talk_base::StreamResult ReadFrameHeader(CapturedFrame* frame);
+
+  // Read a frame and determine how long to wait for the next frame. If the
+  // frame is read successfully, Set the output parameter, wait_time_ms and
+  // return true. Otherwise, do not change wait_time_ms and return false.
+  bool ReadFrame(bool first_frame, int* wait_time_ms);
+
+  // Return the CapturedFrame - useful for extracting contents after reading
+  // a frame. Should be used only while still reading a file (i.e. only while
+  // the CapturedFrame object still exists).
+  const CapturedFrame* frame() const {
+    return &captured_frame_;
+  }
+
+ private:
+  class FileReadThread;  // Forward declaration, defined in .cc.
+
+  talk_base::FileStream video_file_;
+  CapturedFrame captured_frame_;
+  // The number of bytes allocated buffer for captured_frame_.data.
+  uint32 frame_buffer_size_;
+  FileReadThread* file_read_thread_;
+  int repeat_;  // How many times to repeat the file.
+  int64 start_time_ns_;  // Time when the file video capturer starts.
+  int64 last_frame_timestamp_ns_;  // Timestamp of last read frame.
+  bool ignore_framerate_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileVideoCapturer);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FILEVIDEOCAPTURER_H_
diff --git a/talk/session/phone/filevideocapturer_unittest.cc b/talk/session/phone/filevideocapturer_unittest.cc
new file mode 100644
index 0000000..8692703
--- /dev/null
+++ b/talk/session/phone/filevideocapturer_unittest.cc
@@ -0,0 +1,175 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/filevideocapturer.h"
+#include "talk/session/phone/testutils.h"
+
+namespace {
+
+class FileVideoCapturerTest : public testing::Test {
+ public:
+  virtual void SetUp() {
+    capturer_.reset(new cricket::FileVideoCapturer);
+  }
+
+  bool OpenFile(const std::string& filename) {
+    return capturer_->Init(cricket::GetTestFilePath(filename));
+  }
+
+ protected:
+  class VideoCapturerListener : public sigslot::has_slots<> {
+   public:
+    VideoCapturerListener()
+        : frame_count_(0),
+          frame_width_(0),
+          frame_height_(0),
+          resolution_changed_(false) {
+    }
+
+    void OnFrameCaptured(cricket::VideoCapturer* capturer,
+                         const cricket::CapturedFrame* frame) {
+      ++frame_count_;
+      if (1 == frame_count_) {
+        frame_width_ = frame->width;
+        frame_height_ = frame->height;
+      } else if (frame_width_ != frame->width ||
+          frame_height_ != frame->height) {
+        resolution_changed_ = true;
+      }
+    }
+
+    int frame_count() const { return frame_count_; }
+    int frame_width() const { return frame_width_; }
+    int frame_height() const { return frame_height_; }
+    bool resolution_changed() const { return resolution_changed_; }
+
+   private:
+    int frame_count_;
+    int frame_width_;
+    int frame_height_;
+    bool resolution_changed_;
+  };
+
+  talk_base::scoped_ptr<cricket::FileVideoCapturer> capturer_;
+  cricket::VideoFormat capture_format_;
+};
+
+TEST_F(FileVideoCapturerTest, TestNotOpened) {
+  EXPECT_EQ("", capturer_->GetId());
+  EXPECT_EQ(NULL, capturer_->GetSupportedFormats());
+  EXPECT_EQ(NULL, capturer_->GetCaptureFormat());
+  EXPECT_FALSE(capturer_->IsRunning());
+}
+
+TEST_F(FileVideoCapturerTest, TestInvalidOpen) {
+  EXPECT_FALSE(OpenFile("NotmeNotme"));
+}
+
+TEST_F(FileVideoCapturerTest, TestOpen) {
+  EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames"));
+  EXPECT_NE("", capturer_->GetId());
+  EXPECT_TRUE(NULL != capturer_->GetSupportedFormats());
+  EXPECT_EQ(1U, capturer_->GetSupportedFormats()->size());
+  EXPECT_EQ(NULL, capturer_->GetCaptureFormat());  // not started yet
+  EXPECT_FALSE(capturer_->IsRunning());
+}
+
+TEST_F(FileVideoCapturerTest, TestLargeSmallDesiredFormat) {
+  EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames"));
+  // desired format with large resolution.
+  cricket::VideoFormat desired(
+      3200, 2400, cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_ANY);
+  EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_));
+  EXPECT_EQ(320, capture_format_.width);
+  EXPECT_EQ(240, capture_format_.height);
+
+  // Desired format with small resolution.
+  desired.width = 0;
+  desired.height = 0;
+  EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_));
+  EXPECT_EQ(320, capture_format_.width);
+  EXPECT_EQ(240, capture_format_.height);
+}
+
+TEST_F(FileVideoCapturerTest, TestSupportedAsDesiredFormat) {
+  EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames"));
+  // desired format same as the capture format supported by the file
+  cricket::VideoFormat desired = capturer_->GetSupportedFormats()->at(0);
+  EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_));
+  EXPECT_TRUE(desired == capture_format_);
+
+  // desired format same as the supported capture format except the fourcc
+  desired.fourcc = cricket::FOURCC_ANY;
+  EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_));
+  EXPECT_NE(capture_format_.fourcc, desired.fourcc);
+
+  // desired format with minimum interval
+  desired.interval = cricket::VideoFormat::kMinimumInterval;
+  EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_));
+}
+
+TEST_F(FileVideoCapturerTest, TestNoRepeat) {
+  EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames"));
+  VideoCapturerListener listener;
+  capturer_->SignalFrameCaptured.connect(
+      &listener, &VideoCapturerListener::OnFrameCaptured);
+  capturer_->set_repeat(0);
+  capture_format_ = capturer_->GetSupportedFormats()->at(0);
+  EXPECT_EQ(cricket::CR_SUCCESS, capturer_->Start(capture_format_));
+  EXPECT_TRUE_WAIT(!capturer_->IsRunning(), 20000);
+  EXPECT_EQ(48, listener.frame_count());
+}
+
+TEST_F(FileVideoCapturerTest, TestRepeatForever) {
+  // Start the capturer_ with 50 fps and read no less than 150 frames.
+  EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames"));
+  VideoCapturerListener listener;
+  capturer_->SignalFrameCaptured.connect(
+      &listener, &VideoCapturerListener::OnFrameCaptured);
+  capturer_->set_repeat(talk_base::kForever);
+  capture_format_ = capturer_->GetSupportedFormats()->at(0);
+  capture_format_.interval = cricket::VideoFormat::FpsToInterval(50);
+  EXPECT_EQ(cricket::CR_SUCCESS, capturer_->Start(capture_format_));
+  EXPECT_TRUE(NULL != capturer_->GetCaptureFormat());
+  EXPECT_TRUE(capture_format_ == *capturer_->GetCaptureFormat());
+  EXPECT_TRUE_WAIT(!capturer_->IsRunning() ||
+                   listener.frame_count() >= 150, 20000);
+  capturer_->Stop();
+  EXPECT_FALSE(capturer_->IsRunning());
+  EXPECT_GE(listener.frame_count(), 150);
+  EXPECT_FALSE(listener.resolution_changed());
+  EXPECT_EQ(listener.frame_width(), capture_format_.width);
+  EXPECT_EQ(listener.frame_height(), capture_format_.height);
+}
+
+}  // unnamed namespace
diff --git a/talk/session/phone/linuxdevicemanager.cc b/talk/session/phone/linuxdevicemanager.cc
new file mode 100644
index 0000000..6bad677
--- /dev/null
+++ b/talk/session/phone/linuxdevicemanager.cc
@@ -0,0 +1,410 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/linuxdevicemanager.h"
+
+#include <libudev.h>
+#include <unistd.h>
+#include "talk/base/linux.h"
+#include "talk/base/logging.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/libudevsymboltable.h"
+#include "talk/session/phone/mediacommon.h"
+#include "talk/session/phone/v4llookup.h"
+#include "talk/sound/platformsoundsystem.h"
+#include "talk/sound/platformsoundsystemfactory.h"
+#include "talk/sound/sounddevicelocator.h"
+#include "talk/sound/soundsysteminterface.h"
+
+namespace cricket {
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+  return new LinuxDeviceManager();
+}
+
+class LinuxDeviceWatcher
+    : public DeviceWatcher,
+      private talk_base::Dispatcher {
+ public:
+  explicit LinuxDeviceWatcher(DeviceManagerInterface* dm);
+  virtual ~LinuxDeviceWatcher();
+  virtual bool Start();
+  virtual void Stop();
+
+ private:
+  virtual uint32 GetRequestedEvents();
+  virtual void OnPreEvent(uint32 ff);
+  virtual void OnEvent(uint32 ff, int err);
+  virtual int GetDescriptor();
+  virtual bool IsDescriptorClosed();
+
+  DeviceManagerInterface* manager_;
+  LibUDevSymbolTable libudev_;
+  struct udev* udev_;
+  struct udev_monitor* udev_monitor_;
+  bool registered_;
+};
+
+#define LATE(sym) LATESYM_GET(LibUDevSymbolTable, &libudev_, sym)
+
+static const char* const kFilteredAudioDevicesName[] = {
+#if defined(CHROMEOS)
+    "surround40:",
+    "surround41:",
+    "surround50:",
+    "surround51:",
+    "surround71:",
+    "iec958:",      // S/PDIF
+#endif
+    NULL,
+};
+static const char* kFilteredVideoDevicesName[] = {
+    NULL,
+};
+
+LinuxDeviceManager::LinuxDeviceManager()
+    : sound_system_(new PlatformSoundSystemFactory()) {
+  set_watcher(new LinuxDeviceWatcher(this));
+}
+
+LinuxDeviceManager::~LinuxDeviceManager() {
+}
+
+bool LinuxDeviceManager::GetAudioDevices(bool input,
+                                         std::vector<Device>* devs) {
+  devs->clear();
+  if (!sound_system_.get()) {
+    return false;
+  }
+  SoundSystemInterface::SoundDeviceLocatorList list;
+  bool success;
+  if (input) {
+    success = sound_system_->EnumerateCaptureDevices(&list);
+  } else {
+    success = sound_system_->EnumeratePlaybackDevices(&list);
+  }
+  if (!success) {
+    LOG(LS_ERROR) << "Can't enumerate devices";
+    sound_system_.release();
+    return false;
+  }
+  // We have to start the index at 1 because GIPS VoiceEngine puts the default
+  // device at index 0, but Enumerate(Capture|Playback)Devices does not include
+  // a locator for the default device.
+  int index = 1;
+  for (SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin();
+       i != list.end();
+       ++i, ++index) {
+    devs->push_back(Device((*i)->name(), index));
+  }
+  SoundSystemInterface::ClearSoundDeviceLocatorList(&list);
+  sound_system_.release();
+  return FilterDevices(devs, kFilteredAudioDevicesName);
+}
+
+static const std::string kVideoMetaPathK2_4("/proc/video/dev/");
+static const std::string kVideoMetaPathK2_6("/sys/class/video4linux/");
+
+enum MetaType { M2_4, M2_6, NONE };
+
+static void ScanDeviceDirectory(const std::string& devdir,
+                                std::vector<Device>* devices) {
+  talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
+      talk_base::Filesystem::IterateDirectory());
+
+  if (directoryIterator->Iterate(talk_base::Pathname(devdir))) {
+    do {
+      std::string filename = directoryIterator->Name();
+      std::string device_name = devdir + filename;
+      if (!directoryIterator->IsDots()) {
+        if (filename.find("video") == 0 &&
+            V4LLookup::IsV4L2Device(device_name)) {
+          devices->push_back(Device(device_name, device_name));
+        }
+      }
+    } while (directoryIterator->Next());
+  }
+}
+
+static std::string GetVideoDeviceNameK2_6(const std::string& device_meta_path) {
+  std::string device_name;
+
+  talk_base::scoped_ptr<talk_base::FileStream> device_meta_stream(
+      talk_base::Filesystem::OpenFile(device_meta_path, "r"));
+
+  if (device_meta_stream.get() != NULL) {
+    if (device_meta_stream->ReadLine(&device_name) != talk_base::SR_SUCCESS) {
+      LOG(LS_ERROR) << "Failed to read V4L2 device meta " << device_meta_path;
+    }
+    device_meta_stream->Close();
+  }
+
+  return device_name;
+}
+
+static std::string Trim(const std::string& s, const std::string& drop = " \t") {
+  std::string::size_type first = s.find_first_not_of(drop);
+  std::string::size_type last  = s.find_last_not_of(drop);
+
+  if (first == std::string::npos || last == std::string::npos)
+    return std::string("");
+
+  return s.substr(first, last - first + 1);
+}
+
+static std::string GetVideoDeviceNameK2_4(const std::string& device_meta_path) {
+  talk_base::ConfigParser::MapVector all_values;
+
+  talk_base::ConfigParser config_parser;
+  talk_base::FileStream* file_stream =
+      talk_base::Filesystem::OpenFile(device_meta_path, "r");
+
+  if (file_stream == NULL) return "";
+
+  config_parser.Attach(file_stream);
+  config_parser.Parse(&all_values);
+
+  for (talk_base::ConfigParser::MapVector::iterator i = all_values.begin();
+      i != all_values.end(); ++i) {
+    talk_base::ConfigParser::SimpleMap::iterator device_name_i =
+        i->find("name");
+
+    if (device_name_i != i->end()) {
+      return device_name_i->second;
+    }
+  }
+
+  return "";
+}
+
+static std::string GetVideoDeviceName(MetaType meta,
+    const std::string& device_file_name) {
+  std::string device_meta_path;
+  std::string device_name;
+  std::string meta_file_path;
+
+  if (meta == M2_6) {
+    meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/name";
+
+    LOG(LS_INFO) << "Trying " + meta_file_path;
+    device_name = GetVideoDeviceNameK2_6(meta_file_path);
+
+    if (device_name.empty()) {
+      meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/model";
+
+      LOG(LS_INFO) << "Trying " << meta_file_path;
+      device_name = GetVideoDeviceNameK2_6(meta_file_path);
+    }
+  } else {
+    meta_file_path = kVideoMetaPathK2_4 + device_file_name;
+    LOG(LS_INFO) << "Trying " << meta_file_path;
+    device_name = GetVideoDeviceNameK2_4(meta_file_path);
+  }
+
+  if (device_name.empty()) {
+    device_name = "/dev/" + device_file_name;
+    LOG(LS_ERROR)
+      << "Device name not found, defaulting to device path " << device_name;
+  }
+
+  LOG(LS_INFO) << "Name for " << device_file_name << " is " << device_name;
+
+  return Trim(device_name);
+}
+
+static void ScanV4L2Devices(std::vector<Device>* devices) {
+  LOG(LS_INFO) << ("Enumerating V4L2 devices");
+
+  MetaType meta;
+  std::string metadata_dir;
+
+  talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
+      talk_base::Filesystem::IterateDirectory());
+
+  // Try and guess kernel version
+  if (directoryIterator->Iterate(kVideoMetaPathK2_6)) {
+    meta = M2_6;
+    metadata_dir = kVideoMetaPathK2_6;
+  } else if (directoryIterator->Iterate(kVideoMetaPathK2_4)) {
+    meta = M2_4;
+    metadata_dir = kVideoMetaPathK2_4;
+  } else {
+    meta = NONE;
+  }
+
+  if (meta != NONE) {
+    LOG(LS_INFO) << "V4L2 device metadata found at " << metadata_dir;
+
+    do {
+      std::string filename = directoryIterator->Name();
+
+      if (filename.find("video") == 0) {
+        std::string device_path = "/dev/" + filename;
+
+        if (V4LLookup::IsV4L2Device(device_path)) {
+          devices->push_back(
+              Device(GetVideoDeviceName(meta, filename), device_path));
+        }
+      }
+    } while (directoryIterator->Next());
+  } else {
+    LOG(LS_ERROR) << "Unable to detect v4l2 metadata directory";
+  }
+
+  if (devices->size() == 0) {
+    LOG(LS_INFO) << "Plan B. Scanning all video devices in /dev directory";
+    ScanDeviceDirectory("/dev/", devices);
+  }
+
+  LOG(LS_INFO) << "Total V4L2 devices found : " << devices->size();
+}
+
+bool LinuxDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
+  devices->clear();
+  ScanV4L2Devices(devices);
+  return FilterDevices(devices, kFilteredVideoDevicesName);
+}
+
+LinuxDeviceWatcher::LinuxDeviceWatcher(DeviceManagerInterface* dm)
+    : DeviceWatcher(dm),
+      manager_(dm),
+      udev_(NULL),
+      udev_monitor_(NULL),
+      registered_(false) {
+}
+
+LinuxDeviceWatcher::~LinuxDeviceWatcher() {
+}
+
+bool LinuxDeviceWatcher::Start() {
+  // We deliberately return true in the failure paths here because libudev is
+  // not a critical component of a Linux system so it may not be present/usable,
+  // and we don't want to halt LinuxDeviceManager initialization in such a case.
+  if (!libudev_.Load()) {
+    LOG(LS_WARNING) << "libudev not present/usable; LinuxDeviceWatcher disabled";
+    return true;
+  }
+  udev_ = LATE(udev_new)();
+  if (!udev_) {
+    LOG_ERR(LS_ERROR) << "udev_new()";
+    return true;
+  }
+  // The second argument here is the event source. It can be either "kernel" or
+  // "udev", but "udev" is the only correct choice. Apps listen on udev and the
+  // udev daemon in turn listens on the kernel.
+  udev_monitor_ = LATE(udev_monitor_new_from_netlink)(udev_, "udev");
+  if (!udev_monitor_) {
+    LOG_ERR(LS_ERROR) << "udev_monitor_new_from_netlink()";
+    return true;
+  }
+  // We only listen for changes in the video devices. Audio devices are more or
+  // less unimportant because receiving device change notifications really only
+  // matters for broadcasting updated send/recv capabilities based on whether
+  // there is at least one device available, and almost all computers have at
+  // least one audio device. Also, PulseAudio device notifications don't come
+  // from the udev daemon, they come from the PulseAudio daemon, so we'd only
+  // want to listen for audio device changes from udev if using ALSA. For
+  // simplicity, we don't bother with any audio stuff at all.
+  if (LATE(udev_monitor_filter_add_match_subsystem_devtype)(udev_monitor_,
+                                                            "video4linux",
+                                                            NULL) < 0) {
+    LOG_ERR(LS_ERROR) << "udev_monitor_filter_add_match_subsystem_devtype()";
+    return true;
+  }
+  if (LATE(udev_monitor_enable_receiving)(udev_monitor_) < 0) {
+    LOG_ERR(LS_ERROR) << "udev_monitor_enable_receiving()";
+    return true;
+  }
+  static_cast<talk_base::PhysicalSocketServer*>(
+      talk_base::Thread::Current()->socketserver())->Add(this);
+  registered_ = true;
+  return true;
+}
+
+void LinuxDeviceWatcher::Stop() {
+  if (registered_) {
+    static_cast<talk_base::PhysicalSocketServer*>(
+        talk_base::Thread::Current()->socketserver())->Remove(this);
+    registered_ = false;
+  }
+  if (udev_monitor_) {
+    LATE(udev_monitor_unref)(udev_monitor_);
+    udev_monitor_ = NULL;
+  }
+  if (udev_) {
+    LATE(udev_unref)(udev_);
+    udev_ = NULL;
+  }
+  libudev_.Unload();
+}
+
+uint32 LinuxDeviceWatcher::GetRequestedEvents() {
+  return talk_base::DE_READ;
+}
+
+void LinuxDeviceWatcher::OnPreEvent(uint32 ff) {
+  // Nothing to do.
+}
+
+void LinuxDeviceWatcher::OnEvent(uint32 ff, int err) {
+  udev_device* device = LATE(udev_monitor_receive_device)(udev_monitor_);
+  if (!device) {
+    // Probably the socket connection to the udev daemon was terminated (perhaps
+    // the daemon crashed or is being restarted?).
+    LOG_ERR(LS_WARNING) << "udev_monitor_receive_device()";
+    // Stop listening to avoid potential livelock (an fd with EOF in it is
+    // always considered readable).
+    static_cast<talk_base::PhysicalSocketServer*>(
+        talk_base::Thread::Current()->socketserver())->Remove(this);
+    registered_ = false;
+    return;
+  }
+  // Else we read the device successfully.
+
+  // Since we already have our own filesystem-based device enumeration code, we
+  // simply re-enumerate rather than inspecting the device event.
+  LATE(udev_device_unref)(device);
+  manager_->SignalDevicesChange();
+}
+
+int LinuxDeviceWatcher::GetDescriptor() {
+  return LATE(udev_monitor_get_fd)(udev_monitor_);
+}
+
+bool LinuxDeviceWatcher::IsDescriptorClosed() {
+  // If it is closed then we will just get an error in
+  // udev_monitor_receive_device and unregister, so we don't need to check for
+  // it separately.
+  return false;
+}
+
+};  // namespace cricket
diff --git a/talk/session/phone/linuxdevicemanager.h b/talk/session/phone/linuxdevicemanager.h
new file mode 100644
index 0000000..dce3286
--- /dev/null
+++ b/talk/session/phone/linuxdevicemanager.h
@@ -0,0 +1,55 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_LINUXDEVICEMANAGER_H_
+#define TALK_SESSION_PHONE_LINUXDEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/phone/devicemanager.h"
+#include "talk/sound/soundsystemfactory.h"
+
+namespace cricket {
+
+class LinuxDeviceManager : public DeviceManager {
+ public:
+  LinuxDeviceManager();
+  virtual ~LinuxDeviceManager();
+
+  virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
+
+ private:
+  virtual bool GetAudioDevices(bool input, std::vector<Device>* devs);
+  SoundSystemHandle sound_system_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_LINUXDEVICEMANAGER_H_
diff --git a/talk/session/phone/macdevicemanager.cc b/talk/session/phone/macdevicemanager.cc
new file mode 100644
index 0000000..bad5ea9
--- /dev/null
+++ b/talk/session/phone/macdevicemanager.cc
@@ -0,0 +1,198 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/macdevicemanager.h"
+
+#include <CoreAudio/CoreAudio.h>
+#include <QuickTime/QuickTime.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/mediacommon.h"
+
+class DeviceWatcherImpl;
+
+namespace cricket {
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+  return new MacDeviceManager();
+}
+
+class MacDeviceWatcher : public DeviceWatcher {
+ public:
+  explicit MacDeviceWatcher(DeviceManagerInterface* dm);
+  virtual ~MacDeviceWatcher();
+  virtual bool Start();
+  virtual void Stop();
+
+ private:
+  DeviceManagerInterface* manager_;
+  DeviceWatcherImpl* impl_;
+};
+
+static const char* kFilteredAudioDevicesName[] = {
+    NULL,
+};
+// TODO: Try to get hold of a copy of Final Cut to understand why we
+//               crash while scanning their components on OS X.
+static const char* const kFilteredVideoDevicesName[] =  {
+    "Google Camera Adapter",   // Our own magiccams
+    "DVCPRO HD",               // Final cut
+    "Sonix SN9C201p",          // Crashes in OpenAComponent and CloseComponent
+    NULL,
+};
+static const int kVideoDeviceOpenAttempts = 3;
+static const UInt32 kAudioDeviceNameLength = 64;
+// Obj-C functions defined in macdevicemanagermm.mm
+// TODO: have a shared header for these function defines.
+extern DeviceWatcherImpl* CreateDeviceWatcherCallback(
+    DeviceManagerInterface* dm);
+extern void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* impl);
+extern bool GetQTKitVideoDevices(std::vector<Device>* out);
+static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out);
+static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out);
+
+MacDeviceManager::MacDeviceManager() {
+  set_watcher(new MacDeviceWatcher(this));
+}
+
+MacDeviceManager::~MacDeviceManager() {
+}
+
+bool MacDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
+  devices->clear();
+  if (!GetQTKitVideoDevices(devices)) {
+    return false;
+  }
+  return FilterDevices(devices, kFilteredVideoDevicesName);
+}
+
+bool MacDeviceManager::GetAudioDevices(bool input,
+                                       std::vector<Device>* devs) {
+  devs->clear();
+  std::vector<AudioDeviceID> dev_ids;
+  bool ret = GetAudioDeviceIDs(input, &dev_ids);
+  if (!ret) {
+    return false;
+  }
+  for (size_t i = 0; i < dev_ids.size(); ++i) {
+    std::string name;
+    if (GetAudioDeviceName(dev_ids[i], input, &name)) {
+      devs->push_back(Device(name, dev_ids[i]));
+    }
+  }
+  return FilterDevices(devs, kFilteredAudioDevicesName);
+}
+
+static bool GetAudioDeviceIDs(bool input,
+                              std::vector<AudioDeviceID>* out_dev_ids) {
+  UInt32 propsize;
+  OSErr err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
+                                           &propsize, NULL);
+  if (0 != err) {
+    LOG(LS_ERROR) << "Couldn't get information about property, "
+                  << "so no device list acquired.";
+    return false;
+  }
+
+  size_t num_devices = propsize / sizeof(AudioDeviceID);
+  talk_base::scoped_array<AudioDeviceID> device_ids(
+      new AudioDeviceID[num_devices]);
+
+  err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+                                 &propsize, device_ids.get());
+  if (0 != err) {
+    LOG(LS_ERROR) << "Failed to get device ids, "
+                  << "so no device listing acquired.";
+    return false;
+  }
+
+  for (size_t i = 0; i < num_devices; ++i) {
+    AudioDeviceID an_id = device_ids[i];
+    // find out the number of channels for this direction
+    // (input/output) on this device -
+    // we'll ignore anything with no channels.
+    err = AudioDeviceGetPropertyInfo(an_id, 0, input,
+                                     kAudioDevicePropertyStreams,
+                                     &propsize, NULL);
+    if (0 == err) {
+      unsigned num_channels = propsize / sizeof(AudioStreamID);
+      if (0 < num_channels) {
+        out_dev_ids->push_back(an_id);
+      }
+    } else {
+      LOG(LS_ERROR) << "No property info for stream property for device id "
+                    << an_id << "(is_input == " << input
+                    << "), so not including it in the list.";
+    }
+  }
+
+  return true;
+}
+
+static bool GetAudioDeviceName(AudioDeviceID id,
+                               bool input,
+                               std::string* out_name) {
+  UInt32 nameLength = kAudioDeviceNameLength;
+  char name[kAudioDeviceNameLength + 1];
+  OSErr err = AudioDeviceGetProperty(id, 0, input,
+                                     kAudioDevicePropertyDeviceName,
+                                     &nameLength, name);
+  if (0 != err) {
+    LOG(LS_ERROR) << "No name acquired for device id " << id;
+    return false;
+  }
+
+  *out_name = name;
+  return true;
+}
+
+MacDeviceWatcher::MacDeviceWatcher(DeviceManagerInterface* manager)
+    : DeviceWatcher(manager),
+      manager_(manager),
+      impl_(NULL) {
+}
+
+MacDeviceWatcher::~MacDeviceWatcher() {
+}
+
+bool MacDeviceWatcher::Start() {
+  if (!impl_) {
+    impl_ = CreateDeviceWatcherCallback(manager_);
+  }
+  return impl_ != NULL;
+}
+
+void MacDeviceWatcher::Stop() {
+  if (impl_) {
+    ReleaseDeviceWatcherCallback(impl_);
+    impl_ = NULL;
+  }
+}
+
+};  // namespace cricket
diff --git a/talk/session/phone/macdevicemanager.h b/talk/session/phone/macdevicemanager.h
new file mode 100644
index 0000000..ec4cfc0
--- /dev/null
+++ b/talk/session/phone/macdevicemanager.h
@@ -0,0 +1,56 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_MACDEVICEMANAGER_H_
+#define TALK_SESSION_PHONE_MACDEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/phone/devicemanager.h"
+
+namespace cricket {
+
+class DeviceWatcher;
+
+class MacDeviceManager : public DeviceManager {
+ public:
+  MacDeviceManager();
+  virtual ~MacDeviceManager();
+
+  virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
+
+ private:
+  virtual bool GetAudioDevices(bool input, std::vector<Device>* devs);
+  bool FilterDevice(const Device& d);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_MACDEVICEMANAGER_H_
diff --git a/talk/session/phone/macdevicemanagermm.mm b/talk/session/phone/macdevicemanagermm.mm
new file mode 100644
index 0000000..db970c2
--- /dev/null
+++ b/talk/session/phone/macdevicemanagermm.mm
@@ -0,0 +1,116 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/devicemanager.h"
+
+#import <assert.h>
+#import <QTKit/QTKit.h>
+
+#include "talk/base/logging.h"
+
+@interface DeviceWatcherImpl : NSObject {
+ @private
+  cricket::DeviceManagerInterface* manager_;
+}
+- (id)init:(cricket::DeviceManagerInterface*)manager;
+- (void)onDevicesChanged:(NSNotification *)notification;
+@end
+
+@implementation DeviceWatcherImpl
+- (id)init:(cricket::DeviceManagerInterface*)manager {
+  if ((self = [super init])) {
+    assert(manager != NULL);
+    manager_ = manager;
+    [[NSNotificationCenter defaultCenter] addObserver:self
+        selector:@selector(onDevicesChanged:)
+        name:QTCaptureDeviceWasConnectedNotification
+        object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self
+        selector:@selector(onDevicesChanged:)
+        name:QTCaptureDeviceWasDisconnectedNotification
+        object:nil];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [[NSNotificationCenter defaultCenter] removeObserver:self];
+  [super dealloc];
+}
+- (void)onDevicesChanged:(NSNotification *)notification {
+  manager_->SignalDevicesChange();
+}
+@end
+
+namespace cricket {
+
+DeviceWatcherImpl* CreateDeviceWatcherCallback(
+    DeviceManagerInterface* manager) {
+  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+  DeviceWatcherImpl* impl = [[DeviceWatcherImpl alloc] init:manager];
+  [pool drain];
+  return impl;
+}
+
+void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* watcher) {
+  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+  [watcher release];
+  [pool drain];
+}
+
+bool GetQTKitVideoDevices(std::vector<Device>* devices) {
+  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+  NSArray* qt_capture_devices =
+      [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
+  NSUInteger count = [qt_capture_devices count];
+  LOG(LS_INFO) << count << " capture device(s) found:";
+  for (QTCaptureDevice* qt_capture_device in qt_capture_devices) {
+    static NSString* const kFormat = @"localizedDisplayName: \"%@\", "
+        @"modelUniqueID: \"%@\", uniqueID \"%@\", isConnected: %d, isOpen: %d, "
+        @"isInUseByAnotherApplication: %d";
+    NSString* info = [NSString stringWithFormat:kFormat,
+        [qt_capture_device localizedDisplayName],
+        [qt_capture_device modelUniqueID],
+        [qt_capture_device uniqueID],
+        [qt_capture_device isConnected],
+        [qt_capture_device isOpen],
+        [qt_capture_device isInUseByAnotherApplication]];
+    LOG(LS_INFO) << [info UTF8String];
+
+    std::string name([[qt_capture_device localizedDisplayName]
+                         UTF8String]);
+    devices->push_back(Device(name,
+       [[qt_capture_device uniqueID]
+           UTF8String]));
+  }
+
+  [pool drain];
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/mediachannel.h b/talk/session/phone/mediachannel.h
index aa32671..cf86ce6 100644
--- a/talk/session/phone/mediachannel.h
+++ b/talk/session/phone/mediachannel.h
@@ -34,6 +34,7 @@
 #include "talk/base/basictypes.h"
 #include "talk/base/sigslot.h"
 #include "talk/base/socket.h"
+#include "talk/base/window.h"
 #include "talk/session/phone/codec.h"
 // TODO: re-evaluate this include
 #include "talk/session/phone/audiomonitor.h"
@@ -44,6 +45,9 @@
 
 namespace cricket {
 
+class ScreencastId;
+struct StreamParams;
+struct VideoFormat;
 class VideoRenderer;
 
 const int kMinRtpHeaderExtensionId = 1;
@@ -72,13 +76,18 @@
   // TODO: SendRecv direction;
 };
 
-enum VoiceMediaChannelOptions {
-  OPT_CONFERENCE = 0x10000,   // tune the audio stream for conference mode
+enum MediaChannelOptions {
+  // Tune the stream for conference mode.
+  OPT_CONFERENCE = 0x0001
+};
 
+enum VoiceMediaChannelOptions {
+  // Tune the audio stream for vcs with different target levels.
+  OPT_AGC_MINUS_10DB = 0x80000000
 };
 
 enum VideoMediaChannelOptions {
-  // Increase the output framerate by 2x by interpolating frames
+  // Increase the output framerate by 2x by interpolating frames.
   OPT_INTERPOLATE = 0x10000,
   // Enable video adaptation due to cpu load.
   OPT_CPU_ADAPTATION = 0x20000
@@ -109,10 +118,21 @@
   virtual void OnPacketReceived(talk_base::Buffer* packet) = 0;
   // Called when a RTCP packet is received.
   virtual void OnRtcpReceived(talk_base::Buffer* packet) = 0;
-  // Sets the SSRC to be used for outgoing data.
-  virtual void SetSendSsrc(uint32 id) = 0;
-  // Set the CNAME of RTCP
-  virtual bool SetRtcpCName(const std::string& cname) = 0;
+  // Creates a new outgoing media stream with SSRCs and CNAME as described
+  // by sp.
+  virtual bool AddSendStream(const StreamParams& sp) = 0;
+  // Removes an outgoing media stream.
+  // ssrc must be the first SSRC of the media stream if the stream uses
+  // multiple SSRCs.
+  virtual bool RemoveSendStream(uint32 ssrc) = 0;
+  // Creates a new incoming media stream with SSRCs and CNAME as described
+  // by sp.
+  virtual bool AddRecvStream(const StreamParams& sp) = 0;
+  // Removes an incoming media stream.
+  // ssrc must be the first SSRC of the media stream if the stream uses
+  // multiple SSRCs.
+  virtual bool RemoveRecvStream(uint32 ssrc) = 0;
+
   // Mutes the channel.
   virtual bool Mute(bool on) = 0;
 
@@ -138,7 +158,24 @@
 };
 
 struct VoiceSenderInfo {
+  VoiceSenderInfo()
+      : ssrc(0),
+        bytes_sent(0),
+        packets_sent(0),
+        packets_lost(0),
+        fraction_lost(0.0),
+        ext_seqnum(0),
+        rtt_ms(0),
+        jitter_ms(0),
+        audio_level(0),
+        echo_delay_median_ms(0),
+        echo_delay_std_ms(0),
+        echo_return_loss(0),
+        echo_return_loss_enhancement(0) {
+  }
+
   uint32 ssrc;
+  std::string codec_name;
   int bytes_sent;
   int packets_sent;
   int packets_lost;
@@ -147,9 +184,27 @@
   int rtt_ms;
   int jitter_ms;
   int audio_level;
+  int echo_delay_median_ms;
+  int echo_delay_std_ms;
+  int echo_return_loss;
+  int echo_return_loss_enhancement;
 };
 
 struct VoiceReceiverInfo {
+  VoiceReceiverInfo()
+      : ssrc(0),
+        bytes_rcvd(0),
+        packets_rcvd(0),
+        packets_lost(0),
+        fraction_lost(0.0),
+        ext_seqnum(0),
+        jitter_ms(0),
+        jitter_buffer_ms(0),
+        jitter_buffer_preferred_ms(0),
+        delay_estimate_ms(0),
+        audio_level(0) {
+  }
+
   uint32 ssrc;
   int bytes_rcvd;
   int packets_rcvd;
@@ -164,7 +219,26 @@
 };
 
 struct VideoSenderInfo {
+  VideoSenderInfo()
+      : ssrc(0),
+        bytes_sent(0),
+        packets_sent(0),
+        packets_cached(0),
+        packets_lost(0),
+        fraction_lost(0.0),
+        firs_rcvd(0),
+        nacks_rcvd(0),
+        rtt_ms(0),
+        frame_width(0),
+        frame_height(0),
+        framerate_input(0),
+        framerate_sent(0),
+        nominal_bitrate(0),
+        preferred_bitrate(0) {
+  }
+
   uint32 ssrc;
+  std::string codec_name;
   int bytes_sent;
   int packets_sent;
   int packets_cached;
@@ -182,6 +256,22 @@
 };
 
 struct VideoReceiverInfo {
+  VideoReceiverInfo()
+      : ssrc(0),
+        bytes_rcvd(0),
+        packets_rcvd(0),
+        packets_lost(0),
+        packets_concealed(0),
+        fraction_lost(0.0),
+        firs_sent(0),
+        nacks_sent(0),
+        frame_width(0),
+        frame_height(0),
+        framerate_rcvd(0),
+        framerate_decoded(0),
+        framerate_output(0) {
+  }
+
   uint32 ssrc;
   int bytes_rcvd;
   // vector<int> layer_bytes_rcvd;
@@ -199,6 +289,16 @@
 };
 
 struct BandwidthEstimationInfo {
+  BandwidthEstimationInfo()
+      : available_send_bandwidth(0),
+        available_recv_bandwidth(0),
+        target_enc_bitrate(0),
+        actual_enc_bitrate(0),
+        retransmit_bitrate(0),
+        transmit_bitrate(0),
+        bucket_delay(0) {
+  }
+
   int available_send_bandwidth;
   int available_recv_bandwidth;
   int target_enc_bitrate;
@@ -261,10 +361,6 @@
   virtual bool SetPlayout(bool playout) = 0;
   // Starts or stops sending (and potentially capture) of local audio.
   virtual bool SetSend(SendFlags flag) = 0;
-  // Adds a new receive-only stream with the specified SSRC.
-  virtual bool AddStream(uint32 ssrc) = 0;
-  // Removes a stream added with AddStream.
-  virtual bool RemoveStream(uint32 ssrc) = 0;
   // Gets current energy levels for all incoming streams.
   virtual bool GetActiveStreams(AudioInfo::StreamList* actives) = 0;
   // Get the current energy level of the stream sent to the speaker.
@@ -315,17 +411,17 @@
   virtual bool SetRecvCodecs(const std::vector<VideoCodec> &codecs) = 0;
   // Sets the codecs/payload types to be used for outgoing media.
   virtual bool SetSendCodecs(const std::vector<VideoCodec> &codecs) = 0;
+  // Sets the format of a specified outgoing stream.
+  virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format) = 0;
   // Starts or stops playout of received video.
   virtual bool SetRender(bool render) = 0;
   // Starts or stops transmission (and potentially capture) of local video.
   virtual bool SetSend(bool send) = 0;
-  // Adds a new receive-only stream with the specified SSRC.
-  virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc) = 0;
-  // Removes a stream added with AddStream.
-  virtual bool RemoveStream(uint32 ssrc) = 0;
   // Sets the renderer object to be used for the specified stream.
   // If SSRC is 0, the renderer is used for the 'default' stream.
   virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) = 0;
+  virtual bool AddScreencast(uint32 ssrc, const ScreencastId& id) = 0;
+  virtual bool RemoveScreencast(uint32 ssrc) = 0;
   // Gets quality stats for the channel.
   virtual bool GetStats(VideoMediaInfo* info) = 0;
 
@@ -334,6 +430,8 @@
   // Reuqest each of the remote senders to send an intra frame.
   virtual bool RequestIntraFrame() = 0;
 
+  // Signals events from the currently active window.
+  sigslot::signal2<uint32, talk_base::WindowEvent> SignalScreencastWindowEvent;
   sigslot::signal2<uint32, Error> SignalMediaError;
 
  protected:
diff --git a/talk/session/phone/mediaengine.cc b/talk/session/phone/mediaengine.cc
index 992fc15..05c7274 100644
--- a/talk/session/phone/mediaengine.cc
+++ b/talk/session/phone/mediaengine.cc
@@ -29,6 +29,8 @@
 
 #if defined(HAVE_LINPHONE)
 #include "talk/session/phone/linphonemediaengine.h"
+#elif defined(ANDROID)
+#include "talk/session/phone/androidmediaengine.h"
 #else
 #if defined(HAVE_WEBRTC_VOICE)
 #include "talk/session/phone/webrtcvoiceengine.h"
@@ -47,6 +49,11 @@
 #endif
 
 #if defined(HAVE_WEBRTC_VIDEO)
+template<>
+CompositeMediaEngine<WebRtcVoiceEngine, WebRtcVideoEngine>::
+    CompositeMediaEngine() {
+  video_.SetVoiceEngine(&voice_);
+}
 #define VIDEO_ENG_NAME WebRtcVideoEngine
 #endif
 
@@ -58,6 +65,8 @@
 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
diff --git a/talk/session/phone/mediaengine.h b/talk/session/phone/mediaengine.h
index 9ea808d..cc77099 100644
--- a/talk/session/phone/mediaengine.h
+++ b/talk/session/phone/mediaengine.h
@@ -32,6 +32,7 @@
 #include <CoreAudio/CoreAudio.h>
 #endif
 
+#include <climits>
 #include <string>
 #include <vector>
 
@@ -40,10 +41,19 @@
 #include "talk/session/phone/devicemanager.h"
 #include "talk/session/phone/mediachannel.h"
 #include "talk/session/phone/mediacommon.h"
+#include "talk/session/phone/videoprocessor.h"
 #include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/voiceprocessor.h"
 
 namespace cricket {
 
+// TODO: For now, a hard-coded ssrc is used as the video ssrc.
+// This is because when the video frame is passed to the mediaprocessor for
+// processing, it doesn't have the correct ssrc. Since currently only Tx
+// Video processing is supported, this is ok. When we switch over to trigger
+// from capturer, this should be fixed and this const removed.
+const uint32 kDummyVideoSsrc = 0xFFFFFFFF;
+
 class VideoCapturer;
 
 // MediaEngineInterface is an abstraction of a media engine which can be
@@ -58,7 +68,6 @@
     ECHO_CANCELLATION = 1 << 0,
     AUTO_GAIN_CONTROL = 1 << 1,
     NOISE_SUPPRESSION = 1 << 2,
-    TYPING_DETECTION = 1 << 3,
     DEFAULT_AUDIO_OPTIONS = ECHO_CANCELLATION | AUTO_GAIN_CONTROL
   };
   enum VideoOptions {
@@ -100,6 +109,9 @@
   virtual bool SetSoundDevices(const Device* in_device,
                                const Device* out_device) = 0;
   virtual bool SetVideoCaptureDevice(const Device* cam_device) = 0;
+  // Sets the externally provided video capturer. The ssrc is the ssrc of the
+  // (video) stream for which the video capturer should be set.
+  virtual bool SetVideoCapturer(VideoCapturer* capturer, uint32 ssrc) = 0;
 
   // Device configuration
   // Gets the current speaker volume, as a value between 0 and 255.
@@ -126,6 +138,16 @@
   virtual void SetVoiceLogging(int min_sev, const char* filter) = 0;
   virtual void SetVideoLogging(int min_sev, const char* filter) = 0;
 
+  // media processors for effects
+  virtual bool RegisterVideoProcessor(VideoProcessor* video_processor) = 0;
+  virtual bool UnregisterVideoProcessor(VideoProcessor* video_processor) = 0;
+  virtual bool RegisterVoiceProcessor(uint32 ssrc,
+                                      VoiceProcessor* video_processor,
+                                      MediaProcessorDirection direction) = 0;
+  virtual bool UnregisterVoiceProcessor(uint32 ssrc,
+                                        VoiceProcessor* video_processor,
+                                        MediaProcessorDirection direction) = 0;
+
   sigslot::repeater2<VideoCapturer*, CaptureResult>
       SignalVideoCaptureResult;
 };
@@ -136,7 +158,6 @@
   static MediaEngineInterface* Create();
 };
 
-
 // CompositeMediaEngine constructs a MediaEngine from separate
 // voice and video engine classes.
 template<class VOICE, class VIDEO>
@@ -189,6 +210,9 @@
   virtual bool SetVideoCaptureDevice(const Device* cam_device) {
     return video_.SetCaptureDevice(cam_device);
   }
+  virtual bool SetVideoCapturer(VideoCapturer* capturer, uint32 ssrc) {
+    return video_.SetVideoCapturer(capturer, ssrc);
+  }
 
   virtual bool GetOutputVolume(int* level) {
     return voice_.GetOutputVolume(level);
@@ -224,6 +248,23 @@
     return video_.SetLogging(min_sev, filter);
   }
 
+  virtual bool RegisterVideoProcessor(VideoProcessor* processor) {
+    return video_.RegisterProcessor(processor);
+  }
+  virtual bool UnregisterVideoProcessor(VideoProcessor* processor) {
+    return video_.UnregisterProcessor(processor);
+  }
+  virtual bool RegisterVoiceProcessor(uint32 ssrc,
+                                      VoiceProcessor* processor,
+                                      MediaProcessorDirection direction) {
+    return voice_.RegisterProcessor(ssrc, processor, direction);
+  }
+  virtual bool UnregisterVoiceProcessor(uint32 ssrc,
+                                        VoiceProcessor* processor,
+                                        MediaProcessorDirection direction) {
+    return voice_.UnregisterProcessor(ssrc, processor, direction);
+  }
+
  protected:
   VOICE voice_;
   VIDEO video_;
@@ -256,6 +297,12 @@
   bool SetLocalMonitor(bool enable) { return true; }
   const std::vector<AudioCodec>& codecs() { return codecs_; }
   void SetLogging(int min_sev, const char* filter) {}
+  bool RegisterProcessor(uint32 ssrc,
+                         VoiceProcessor* voice_processor,
+                         MediaProcessorDirection direction) { return true; }
+  bool UnregisterProcessor(uint32 ssrc,
+                           VoiceProcessor* voice_processor,
+                           MediaProcessorDirection direction) { return true; }
 
  private:
   std::vector<AudioCodec> codecs_;
@@ -282,6 +329,10 @@
   CaptureResult SetCapture(bool capture) { return CR_SUCCESS;  }
   const std::vector<VideoCodec>& codecs() { return codecs_; }
   void SetLogging(int min_sev, const char* filter) {}
+  bool RegisterProcessor(VideoProcessor* video_processor) { return true; }
+  bool UnregisterProcessor(VideoProcessor* video_processor) { return true; }
+  bool SetVideoCapturer(VideoCapturer* capturer, uint32 ssrc) { return true; }
+
   sigslot::signal2<VideoCapturer*, CaptureResult> SignalCaptureResult;
  private:
   std::vector<VideoCodec> codecs_;
diff --git a/talk/session/phone/mediamessages.cc b/talk/session/phone/mediamessages.cc
index 9031351..3dd6982 100644
--- a/talk/session/phone/mediamessages.cc
+++ b/talk/session/phone/mediamessages.cc
@@ -31,55 +31,22 @@
 
 #include "talk/session/phone/mediamessages.h"
 
+#include "talk/base/logging.h"
 #include "talk/base/stringencode.h"
 #include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
 #include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/streamparams.h"
 #include "talk/xmllite/xmlelement.h"
 
 namespace cricket {
 
 namespace {
 
-bool GetFirstSourceByNick(const NamedSources& sources,
-                          const std::string& nick,
-                          NamedSource* source_out) {
-  for (NamedSources::const_iterator source = sources.begin();
-       source != sources.end(); ++source) {
-    if (source->nick == nick) {
-      *source_out = *source;
-      return true;
-    }
-  }
-  return false;
-}
-
-bool GetSourceBySsrc(const NamedSources& sources, uint32 ssrc,
-                     NamedSource* source_out) {
-  for (NamedSources::const_iterator source = sources.begin();
-       source != sources.end(); ++source) {
-    if (source->ssrc == ssrc) {
-      *source_out = *source;
-      return true;
-    }
-  }
-  return false;
-}
-
-// NOTE: There is no check here for duplicate sources, so check before
+// NOTE: There is no check here for duplicate streams, so check before
 // adding.
-void AddSource(NamedSources* sources, const NamedSource& source) {
-  sources->push_back(source);
-}
-
-void RemoveSourceBySsrc(uint32 ssrc, NamedSources* sources) {
-  for (NamedSources::iterator source = sources->begin();
-       source != sources->end(); ) {
-    if (source->ssrc == ssrc) {
-      source = sources->erase(source);
-    } else {
-      ++source;
-    }
-  }
+void AddStream(std::vector<StreamParams>* streams, const StreamParams& stream) {
+  streams->push_back(stream);
 }
 
 bool ParseSsrc(const std::string& string, uint32* ssrc) {
@@ -93,41 +60,14 @@
   return ParseSsrc(element->BodyText(), ssrc);
 }
 
-bool ParseNamedSource(const buzz::XmlElement* source_elem,
-                      NamedSource* named_source,
-                      ParseError* error) {
-  named_source->nick = source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_NICK);
-  if (named_source->nick.empty()) {
-    return BadParse("Missing or invalid nick.", error);
-  }
-
-  named_source->name = source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_NAME);
-  named_source->usage = source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_USAGE);
-  named_source->removed =
-      STR_JINGLE_DRAFT_SOURCE_STATE_REMOVED ==
-      source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_STATE);
-
-  const buzz::XmlElement* ssrc_elem =
-      source_elem->FirstNamed(QN_JINGLE_DRAFT_SOURCE_SSRC);
-  if (ssrc_elem != NULL && !ssrc_elem->BodyText().empty()) {
-    uint32 ssrc;
-    if (!ParseSsrc(ssrc_elem->BodyText(), &ssrc)) {
-      return BadParse("Missing or invalid ssrc.", error);
-    }
-    named_source->SetSsrc(ssrc);
-  }
-
-  return true;
-}
-
 // Builds a <view> element according to the following spec:
 // goto/jinglemuc
 buzz::XmlElement* CreateViewElem(const std::string& name,
                                  const std::string& type) {
   buzz::XmlElement* view_elem =
       new buzz::XmlElement(QN_JINGLE_DRAFT_VIEW, true);
-  view_elem->AddAttr(QN_JINGLE_DRAFT_CONTENT_NAME, name);
-  view_elem->SetAttr(QN_JINGLE_DRAFT_VIEW_TYPE, type);
+  view_elem->AddAttr(QN_NAME, name);
+  view_elem->SetAttr(QN_TYPE, type);
   return view_elem;
 }
 
@@ -144,16 +84,13 @@
                                             const StaticVideoView& view) {
   buzz::XmlElement* view_elem =
       CreateVideoViewElem(content_name, STR_JINGLE_DRAFT_VIEW_TYPE_STATIC);
-  AddXmlAttr(view_elem, QN_JINGLE_DRAFT_VIEW_SSRC, view.ssrc);
+  AddXmlAttr(view_elem, QN_SSRC, view.ssrc);
 
-  buzz::XmlElement* params_elem = new buzz::XmlElement(
-      QN_JINGLE_DRAFT_VIEW_PARAMS);
-  AddXmlAttr(params_elem, QN_JINGLE_DRAFT_VIEW_PARAMS_WIDTH, view.width);
-  AddXmlAttr(params_elem, QN_JINGLE_DRAFT_VIEW_PARAMS_HEIGHT, view.height);
-  AddXmlAttr(params_elem, QN_JINGLE_DRAFT_VIEW_PARAMS_FRAMERATE,
-             view.framerate);
-  AddXmlAttr(params_elem, QN_JINGLE_DRAFT_VIEW_PARAMS_PREFERENCE,
-             view.preference);
+  buzz::XmlElement* params_elem = new buzz::XmlElement(QN_JINGLE_DRAFT_PARAMS);
+  AddXmlAttr(params_elem, QN_WIDTH, view.width);
+  AddXmlAttr(params_elem, QN_HEIGHT, view.height);
+  AddXmlAttr(params_elem, QN_FRAMERATE, view.framerate);
+  AddXmlAttr(params_elem, QN_PREFERENCE, view.preference);
   view_elem->AddElement(params_elem);
 
   return view_elem;
@@ -161,49 +98,100 @@
 
 }  //  namespace
 
-bool MediaSources::GetFirstAudioSourceByNick(
-    const std::string& nick, NamedSource* source) {
-  return GetFirstSourceByNick(audio_, nick, source);
+bool MediaStreams::GetAudioStreamByNickAndName(
+    const std::string& nick, const std::string& name, StreamParams* stream) {
+  return GetStreamByNickAndName(audio_, nick, name, stream);
 }
 
-bool MediaSources::GetFirstVideoSourceByNick(
-    const std::string& nick, NamedSource* source) {
-  return GetFirstSourceByNick(video_, nick, source);
+bool MediaStreams::GetVideoStreamByNickAndName(
+    const std::string& nick, const std::string& name, StreamParams* stream) {
+  return GetStreamByNickAndName(video_, nick, name, stream);
 }
 
-void MediaSources::CopyFrom(const MediaSources& sources) {
-  audio_ = sources.audio_;
-  video_ = sources.video_;
+void MediaStreams::CopyFrom(const MediaStreams& streams) {
+  audio_ = streams.audio_;
+  video_ = streams.video_;
 }
 
-bool MediaSources::GetAudioSourceBySsrc(uint32 ssrc, NamedSource* source) {
-  return GetSourceBySsrc(audio_, ssrc, source);
+bool MediaStreams::GetAudioStreamBySsrc(uint32 ssrc, StreamParams* stream) {
+  return GetStreamBySsrc(audio_, ssrc, stream);
 }
 
-bool MediaSources::GetVideoSourceBySsrc(uint32 ssrc, NamedSource* source) {
-  return GetSourceBySsrc(video_, ssrc, source);
+bool MediaStreams::GetVideoStreamBySsrc(uint32 ssrc, StreamParams* stream) {
+  return GetStreamBySsrc(video_, ssrc, stream);
 }
 
-void MediaSources::AddAudioSource(const NamedSource& source) {
-  AddSource(&audio_, source);
+void MediaStreams::AddAudioStream(const StreamParams& stream) {
+  AddStream(&audio_, stream);
 }
 
-void MediaSources::AddVideoSource(const NamedSource& source) {
-  AddSource(&video_, source);
+void MediaStreams::AddVideoStream(const StreamParams& stream) {
+  AddStream(&video_, stream);
 }
 
-void MediaSources::RemoveAudioSourceBySsrc(uint32 ssrc) {
-  RemoveSourceBySsrc(ssrc, &audio_);
+void MediaStreams::RemoveAudioStreamByNickAndName(
+    const std::string& nick, const std::string& name) {
+  RemoveStreamByNickAndName(&audio_, nick, name);
 }
 
-void MediaSources::RemoveVideoSourceBySsrc(uint32 ssrc) {
-  RemoveSourceBySsrc(ssrc, &video_);
+void MediaStreams::RemoveVideoStreamByNickAndName(
+    const std::string& nick, const std::string& name) {
+  RemoveStreamByNickAndName(&video_, nick, name);
 }
 
-bool WriteViewRequest(const std::string& content_name,
-                      const ViewRequest& request,
-                      XmlElements* elems,
-                      WriteError* error) {
+bool IsJingleViewRequest(const buzz::XmlElement* action_elem) {
+  return action_elem->FirstNamed(QN_JINGLE_DRAFT_VIEW) != NULL;
+}
+
+bool ParseStaticVideoView(const buzz::XmlElement* view_elem,
+                          StaticVideoView* view,
+                          ParseError* error) {
+  if (!ParseSsrc(view_elem->Attr(QN_SSRC), &(view->ssrc))) {
+    return BadParse("Invalid or missing view ssrc.", error);
+  }
+
+  const buzz::XmlElement* params_elem =
+      view_elem->FirstNamed(QN_JINGLE_DRAFT_PARAMS);
+  if (params_elem) {
+    view->width = GetXmlAttr(params_elem, QN_WIDTH, 0);
+    view->height = GetXmlAttr(params_elem, QN_HEIGHT, 0);
+    view->framerate = GetXmlAttr(params_elem, QN_FRAMERATE, 0);
+    view->preference = GetXmlAttr(params_elem, QN_PREFERENCE, 0);
+  } else {
+    return BadParse("Missing view params.", error);
+  }
+
+  return true;
+}
+
+bool ParseJingleViewRequest(const buzz::XmlElement* action_elem,
+                            ViewRequest* view_request,
+                            ParseError* error) {
+  for (const buzz::XmlElement* view_elem =
+           action_elem->FirstNamed(QN_JINGLE_DRAFT_VIEW);
+       view_elem != NULL;
+       view_elem = view_elem->NextNamed(QN_JINGLE_DRAFT_VIEW)) {
+    std::string type = view_elem->Attr(QN_TYPE);
+    if (STR_JINGLE_DRAFT_VIEW_TYPE_NONE == type) {
+      view_request->static_video_views.clear();
+      return true;
+    } else if (STR_JINGLE_DRAFT_VIEW_TYPE_STATIC == type) {
+      StaticVideoView static_video_view(0, 0, 0, 0);
+      if (!ParseStaticVideoView(view_elem, &static_video_view, error)) {
+        return false;
+      }
+      view_request->static_video_views.push_back(static_video_view);
+    } else {
+      LOG(LS_INFO) << "Ingnoring unknown view type: " << type;
+    }
+  }
+  return true;
+}
+
+bool WriteJingleViewRequest(const std::string& content_name,
+                            const ViewRequest& request,
+                            XmlElements* elems,
+                            WriteError* error) {
   if (request.static_video_views.empty()) {
     elems->push_back(CreateNoneVideoViewElem(content_name));
   } else {
@@ -216,46 +204,150 @@
   return true;
 }
 
-bool IsSourcesNotify(const buzz::XmlElement* action_elem) {
-  return action_elem->FirstNamed(QN_JINGLE_DRAFT_NOTIFY) != NULL;
+bool ParseSsrcAsLegacyStream(const buzz::XmlElement* desc_elem,
+                             std::vector<StreamParams>* streams,
+                             ParseError* error) {
+  const std::string ssrc_str = desc_elem->Attr(QN_SSRC);
+  if (!ssrc_str.empty()) {
+    uint32 ssrc;
+    if (!ParseSsrc(ssrc_str, &ssrc)) {
+      return BadParse("Missing or invalid ssrc.", error);
+    }
+
+    streams->push_back(StreamParams::CreateLegacy(ssrc));
+  }
+  return true;
 }
 
-bool ParseSourcesNotify(const buzz::XmlElement* action_elem,
-                        const SessionDescription* session_description,
-                        MediaSources* sources,
+bool ParseSsrcs(const buzz::XmlElement* parent_elem,
+                std::vector<uint32>* ssrcs,
+                ParseError* error) {
+  for (const buzz::XmlElement* ssrc_elem =
+           parent_elem->FirstNamed(QN_JINGLE_DRAFT_SSRC);
+       ssrc_elem != NULL;
+       ssrc_elem = ssrc_elem->NextNamed(QN_JINGLE_DRAFT_SSRC)) {
+    uint32 ssrc;
+    if (!ParseSsrc(ssrc_elem->BodyText(), &ssrc)) {
+      return BadParse("Missing or invalid ssrc.", error);
+    }
+
+    ssrcs->push_back(ssrc);
+  }
+  return true;
+}
+
+bool ParseSsrcGroups(const buzz::XmlElement* parent_elem,
+                     std::vector<SsrcGroup>* ssrc_groups,
+                     ParseError* error) {
+  for (const buzz::XmlElement* group_elem =
+           parent_elem->FirstNamed(QN_JINGLE_DRAFT_SSRC_GROUP);
+       group_elem != NULL;
+       group_elem = group_elem->NextNamed(QN_JINGLE_DRAFT_SSRC_GROUP)) {
+    std::string semantics = group_elem->Attr(QN_SEMANTICS);
+    std::vector<uint32> ssrcs;
+    if (!ParseSsrcs(group_elem, &ssrcs, error)) {
+      return false;
+    }
+    ssrc_groups->push_back(SsrcGroup(semantics, ssrcs));
+  }
+  return true;
+}
+
+bool ParseJingleStream(const buzz::XmlElement* stream_elem,
+                       std::vector<StreamParams>* streams,
+                       ParseError* error) {
+  StreamParams stream;
+  stream.nick = stream_elem->Attr(QN_NICK);
+  stream.name = stream_elem->Attr(QN_NAME);
+  stream.type = stream_elem->Attr(QN_TYPE);
+  stream.display = stream_elem->Attr(QN_DISPLAY);
+  stream.cname = stream_elem->Attr(QN_CNAME);
+  if (!ParseSsrcs(stream_elem, &(stream.ssrcs), error)) {
+    return false;
+  }
+  std::vector<SsrcGroup> ssrc_groups;
+  if (!ParseSsrcGroups(stream_elem, &(stream.ssrc_groups), error)) {
+    return false;
+  }
+  streams->push_back(stream);
+  return true;
+}
+
+bool HasJingleStreams(const buzz::XmlElement* desc_elem) {
+  const buzz::XmlElement* streams_elem =
+      desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+  return (streams_elem != NULL);
+}
+
+bool ParseJingleStreams(const buzz::XmlElement* desc_elem,
+                        std::vector<StreamParams>* streams,
                         ParseError* error) {
-  for (const buzz::XmlElement* notify_elem =
-           action_elem->FirstNamed(QN_JINGLE_DRAFT_NOTIFY);
-       notify_elem != NULL;
-       notify_elem = notify_elem->NextNamed(QN_JINGLE_DRAFT_NOTIFY)) {
-    std::string content_name = notify_elem->Attr(QN_JINGLE_DRAFT_CONTENT_NAME);
-    for (const buzz::XmlElement* source_elem =
-             notify_elem->FirstNamed(QN_JINGLE_DRAFT_SOURCE);
-         source_elem != NULL;
-         source_elem = source_elem->NextNamed(QN_JINGLE_DRAFT_SOURCE)) {
-      NamedSource named_source;
-      if (!ParseNamedSource(source_elem, &named_source, error)) {
-        return false;
-      }
-
-      if (session_description == NULL) {
-        return BadParse("Unknown content name: " + content_name, error);
-      }
-      const ContentInfo* content =
-          FindContentInfoByName(session_description->contents(), content_name);
-      if (content == NULL) {
-        return BadParse("Unknown content name: " + content_name, error);
-      }
-
-      if (IsAudioContent(content)) {
-        sources->mutable_audio()->push_back(named_source);
-      } else if (IsVideoContent(content)) {
-        sources->mutable_video()->push_back(named_source);
-      }
+  const buzz::XmlElement* streams_elem =
+      desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+  if (streams_elem == NULL) {
+    return BadParse("Missing streams element.", error);
+  }
+  for (const buzz::XmlElement* stream_elem =
+           streams_elem->FirstNamed(QN_JINGLE_DRAFT_STREAM);
+       stream_elem != NULL;
+       stream_elem = stream_elem->NextNamed(QN_JINGLE_DRAFT_STREAM)) {
+    if (!ParseJingleStream(stream_elem, streams, error)) {
+      return false;
     }
   }
-
   return true;
 }
 
+void WriteSsrcs(const std::vector<uint32>& ssrcs,
+                buzz::XmlElement* parent_elem) {
+  for (std::vector<uint32>::const_iterator ssrc = ssrcs.begin();
+       ssrc != ssrcs.end(); ++ssrc) {
+    buzz::XmlElement* ssrc_elem =
+        new buzz::XmlElement(QN_JINGLE_DRAFT_SSRC, false);
+    SetXmlBody(ssrc_elem, *ssrc);
+
+    parent_elem->AddElement(ssrc_elem);
+  }
+}
+
+void WriteSsrcGroups(const std::vector<SsrcGroup>& groups,
+                     buzz::XmlElement* parent_elem) {
+  for (std::vector<SsrcGroup>::const_iterator group = groups.begin();
+       group != groups.end(); ++group) {
+    buzz::XmlElement* group_elem =
+        new buzz::XmlElement(QN_JINGLE_DRAFT_SSRC_GROUP, false);
+    AddXmlAttrIfNonEmpty(group_elem, QN_SEMANTICS, group->semantics);
+    WriteSsrcs(group->ssrcs, group_elem);
+
+    parent_elem->AddElement(group_elem);
+  }
+}
+
+void WriteJingleStream(const StreamParams& stream,
+                       buzz::XmlElement* parent_elem) {
+  buzz::XmlElement* stream_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_STREAM, false);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_NICK, stream.nick);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_NAME, stream.name);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_TYPE, stream.type);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_DISPLAY, stream.display);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_CNAME, stream.cname);
+  WriteSsrcs(stream.ssrcs, stream_elem);
+  WriteSsrcGroups(stream.ssrc_groups, stream_elem);
+
+  parent_elem->AddElement(stream_elem);
+}
+
+void WriteJingleStreams(const std::vector<StreamParams>& streams,
+                        buzz::XmlElement* parent_elem) {
+  buzz::XmlElement* streams_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_STREAMS, true);
+  for (std::vector<StreamParams>::const_iterator stream = streams.begin();
+       stream != streams.end(); ++stream) {
+    WriteJingleStream(*stream, streams_elem);
+  }
+
+  parent_elem->AddElement(streams_elem);
+}
+
 }  // namespace cricket
diff --git a/talk/session/phone/mediamessages.h b/talk/session/phone/mediamessages.h
index 541572f..6f38a64 100644
--- a/talk/session/phone/mediamessages.h
+++ b/talk/session/phone/mediamessages.h
@@ -45,62 +45,50 @@
 
 namespace cricket {
 
-// In a <notify> message, there are number of sources with names.
-// This represents one such named source.
-struct NamedSource {
-  NamedSource() : ssrc(0), ssrc_set(false), removed(false) {}
+struct StreamParams;
 
-  void SetSsrc(uint32 ssrc) {
-    this->ssrc = ssrc;
-    this->ssrc_set = true;
+// A collection of audio and video streams. Most of the methods are
+// merely for convenience. Many of these methods are keyed by ssrc,
+// which is the source identifier in the RTP spec
+// (http://tools.ietf.org/html/rfc3550).
+struct MediaStreams {
+ public:
+  MediaStreams() {}
+  void CopyFrom(const MediaStreams& sources);
+
+  bool empty() const {
+    return audio_.empty() && video_.empty();
   }
 
-  std::string nick;
-  std::string name;
-  std::string usage;
-  uint32 ssrc;
-  bool ssrc_set;
-  bool removed;
-};
+  std::vector<StreamParams>* mutable_audio() { return &audio_; }
+  std::vector<StreamParams>* mutable_video() { return &video_; }
+  const std::vector<StreamParams>& audio() const { return audio_; }
+  const std::vector<StreamParams>& video() const { return video_; }
 
-// TODO: Remove this, according to c++ readability.
-typedef std::vector<NamedSource> NamedSources;
-
-// A collection of named audio sources and named video sources, as
-// would be found in a typical <notify> message.  Most of the methods
-// are merely for convenience. Many of these methods are keyed by
-// ssrc, which is the source identifier in the RTP spec
-// (http://tools.ietf.org/html/rfc3550).
-struct MediaSources {
- public:
-  MediaSources() {}
-  void CopyFrom(const MediaSources& sources);
-
-  NamedSources* mutable_audio() { return &audio_; }
-  NamedSources* mutable_video() { return &video_; }
-  const NamedSources& audio() const { return audio_; }
-  const NamedSources& video() const { return video_; }
-
+  // Remove the streams with the given name.  Names are only unique to
+  // nicks, so you need the nick as well.
+  bool GetAudioStreamByNickAndName(
+      const std::string& nick, const std::string& name, StreamParams* source);
+  bool GetVideoStreamByNickAndName(
+      const std::string& nick, const std::string& name, StreamParams* source);
   // Get the source with the given ssrc.  Returns true if found.
-  bool GetAudioSourceBySsrc(uint32 ssrc, NamedSource* source);
-  bool GetVideoSourceBySsrc(uint32 ssrc, NamedSource* source);
-  // Get the first source with the given nick.  Returns true if found.
-  // TODO: Remove the following two methods once all
-  // senders use explicit-remove by ssrc.
-  bool GetFirstAudioSourceByNick(const std::string& nick, NamedSource* source);
-  bool GetFirstVideoSourceByNick(const std::string& nick, NamedSource* source);
+  bool GetAudioStreamBySsrc(uint32 ssrc, StreamParams* source);
+  bool GetVideoStreamBySsrc(uint32 ssrc, StreamParams* source);
   // Add a source.
-  void AddAudioSource(const NamedSource& source);
-  void AddVideoSource(const NamedSource& source);
-  // Remove the source with the given ssrc.
-  void RemoveAudioSourceBySsrc(uint32 ssrc);
-  void RemoveVideoSourceBySsrc(uint32 ssrc);
+  void AddAudioStream(const StreamParams& source);
+  void AddVideoStream(const StreamParams& source);
+  // Remove the source with the given name.  Names are only unique to
+  // nicks, so you need the nick as well.
+  void RemoveAudioStreamByNickAndName(const std::string& nick,
+                                      const std::string& name);
+  void RemoveVideoStreamByNickAndName(const std::string& nick,
+                                      const std::string& name);
 
  private:
-  NamedSources audio_;
-  NamedSources video_;
+  std::vector<StreamParams> audio_;
+  std::vector<StreamParams> video_;
 
-  DISALLOW_COPY_AND_ASSIGN(MediaSources);
+  DISALLOW_COPY_AND_ASSIGN(MediaStreams);
 };
 
 // In a <view> message, there are a number of views specified.  This
@@ -123,28 +111,45 @@
 
 typedef std::vector<StaticVideoView> StaticVideoViews;
 
-// Represents a whole <view> message, which contains many views.
+// Represents a whole view request message, which contains many views.
 struct ViewRequest {
   StaticVideoViews static_video_views;
 };
 
+// If the parent element (usually <jingle>) is a jingle view.
+bool IsJingleViewRequest(const buzz::XmlElement* action_elem);
+
+// Parses a view request from the parent element (usually
+// <jingle>). If it fails, it returns false and fills an error
+// message.
+bool ParseJingleViewRequest(const buzz::XmlElement* action_elem,
+                            ViewRequest* view_request,
+                            ParseError* error);
+
 // Serializes a view request to XML.  If it fails, returns false and
 // fills in an error message.
-bool WriteViewRequest(const std::string& content_name,
-                      const ViewRequest& view,
-                      XmlElements* elems,
-                      WriteError* error);
+bool WriteJingleViewRequest(const std::string& content_name,
+                            const ViewRequest& view,
+                            XmlElements* elems,
+                            WriteError* error);
 
+// TODO: Get rid of legacy source notify and replace with
+// description-info as soon as reflector is capable of sending it.
 bool IsSourcesNotify(const buzz::XmlElement* action_elem);
 
-// Parses a notify message from XML.  If it fails, returns false and
-// fills in an error message.
-// The session_description is needed to map content_name => media type.
-bool ParseSourcesNotify(const buzz::XmlElement* action_elem,
-                        const SessionDescription* session_description,
-                        MediaSources* sources,
+// If the given elem has <streams>.
+bool HasJingleStreams(const buzz::XmlElement* desc_elem);
+
+// Parses streams from a jingle <description>.  If it fails, returns
+// false and fills an error message.
+bool ParseJingleStreams(const buzz::XmlElement* desc_elem,
+                        std::vector<StreamParams>* streams,
                         ParseError* error);
 
+// Write a <streams> element to the parent_elem.
+void WriteJingleStreams(const std::vector<StreamParams>& streams,
+                        buzz::XmlElement* parent_elem);
+
 }  // namespace cricket
 
 #endif  // TALK_SESSION_PHONE_MEDIAMESSAGES_H_
diff --git a/talk/session/phone/mediamessages_unittest.cc b/talk/session/phone/mediamessages_unittest.cc
new file mode 100644
index 0000000..a76d296
--- /dev/null
+++ b/talk/session/phone/mediamessages_unittest.cc
@@ -0,0 +1,286 @@
+/*
+ * 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/session/phone/mediamessages.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/xmllite/xmlelement.h"
+
+// Unit tests for mediamessages.cc.
+
+namespace cricket {
+
+namespace {
+
+static const char kViewVideoNoneXml[] =
+    "<view xmlns='google:jingle'"
+    "  name='video1'"
+    "  type='none'"
+    "/>";
+
+class MediaMessagesTest : public testing::Test {
+ public:
+  // CreateMediaSessionDescription uses a static variable cricket::NS_JINGLE_RTP
+  // defined in another file and cannot be used to initialize another static
+  // variable (http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14)
+  MediaMessagesTest()
+      : remote_description_(CreateMediaSessionDescription("audio1", "video1")) {
+  }
+
+ protected:
+  static std::string ViewVideoStaticVgaXml(const std::string& ssrc) {
+      return "<view xmlns='google:jingle'"
+             "  name='video1'"
+             "  type='static'"
+             "  ssrc='" + ssrc + "'"
+             ">"
+             "<params"
+             "    width='640'"
+             "    height='480'"
+             "    framerate='30'"
+             "    preference='0'"
+             "  />"
+             "</view>";
+  }
+
+  static cricket::StreamParams CreateStream(const std::string& nick,
+                                            const std::string& name,
+                                            uint32 ssrc1,
+                                            uint32 ssrc2,
+                                            const std::string& semantics,
+                                            const std::string& type,
+                                            const std::string& display) {
+    StreamParams stream;
+    stream.nick = nick;
+    stream.name = name;
+    stream.ssrcs.push_back(ssrc1);
+    stream.ssrcs.push_back(ssrc2);
+    stream.ssrc_groups.push_back(
+        cricket::SsrcGroup(semantics, stream.ssrcs));
+    stream.type = type;
+    stream.display = display;
+    return stream;
+  }
+
+  static std::string StreamsXml(const std::string& stream1,
+                               const std::string& stream2) {
+    return "<streams xmlns='google:jingle'>"
+           + stream1
+           + stream2 +
+           "</streams>";
+  }
+
+
+  static std::string StreamXml(const std::string& nick,
+                               const std::string& name,
+                               const std::string& ssrc1,
+                               const std::string& ssrc2,
+                               const std::string& semantics,
+                               const std::string& type,
+                               const std::string& display) {
+    return "<stream"
+           " nick='" + nick + "'"
+           " name='" + name + "'"
+           " type='" + type + "'"
+           " display='" + display + "'"
+           ">"
+           "<ssrc>" + ssrc1 + "</ssrc>"
+           "<ssrc>" + ssrc2 + "</ssrc>"
+           "<ssrc-group"
+           "  semantics='" + semantics + "'"
+           ">"
+           "<ssrc>" + ssrc1 + "</ssrc>"
+           "<ssrc>" + ssrc2 + "</ssrc>"
+           "</ssrc-group>"
+           "</stream>";
+  }
+
+  static cricket::SessionDescription* CreateMediaSessionDescription(
+      const std::string& audio_content_name,
+      const std::string& video_content_name) {
+    cricket::SessionDescription* desc = new cricket::SessionDescription();
+    desc->AddContent(audio_content_name, cricket::NS_JINGLE_RTP,
+                     new cricket::AudioContentDescription());
+    desc->AddContent(video_content_name, cricket::NS_JINGLE_RTP,
+                     new cricket::VideoContentDescription());
+    return desc;
+  }
+
+  talk_base::scoped_ptr<cricket::SessionDescription> remote_description_;
+};
+
+}  // anonymous namespace
+
+// Test serializing/deserializing an empty <view> message.
+TEST_F(MediaMessagesTest, ViewNoneToFromXml) {
+  buzz::XmlElement* expected_view_elem =
+      buzz::XmlElement::ForStr(kViewVideoNoneXml);
+  talk_base::scoped_ptr<buzz::XmlElement> action_elem(
+      new buzz::XmlElement(QN_JINGLE));
+
+  EXPECT_FALSE(cricket::IsJingleViewRequest(action_elem.get()));
+  action_elem->AddElement(expected_view_elem);
+  EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
+
+  cricket::ViewRequest view_request;
+  cricket::XmlElements actual_view_elems;
+  cricket::WriteError error;
+
+  ASSERT_TRUE(cricket::WriteJingleViewRequest(
+      "video1", view_request, &actual_view_elems, &error));
+
+  ASSERT_EQ(1U, actual_view_elems.size());
+  EXPECT_EQ(expected_view_elem->Str(), actual_view_elems[0]->Str());
+
+  cricket::ParseError parse_error;
+  EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
+  ASSERT_TRUE(cricket::ParseJingleViewRequest(
+      action_elem.get(), &view_request, &parse_error));
+  EXPECT_EQ(0U, view_request.static_video_views.size());
+}
+
+// Test serializing/deserializing an a simple vga <view> message.
+TEST_F(MediaMessagesTest, ViewVgaToFromXml) {
+  talk_base::scoped_ptr<buzz::XmlElement> action_elem(
+      new buzz::XmlElement(QN_JINGLE));
+  buzz::XmlElement* expected_view_elem1 =
+      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("1234"));
+  buzz::XmlElement* expected_view_elem2 =
+      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("2468"));
+  action_elem->AddElement(expected_view_elem1);
+  action_elem->AddElement(expected_view_elem2);
+
+  cricket::ViewRequest view_request;
+  cricket::XmlElements actual_view_elems;
+  cricket::WriteError error;
+
+  view_request.static_video_views.push_back(
+      cricket::StaticVideoView(1234, 640, 480, 30));
+  view_request.static_video_views.push_back(
+      cricket::StaticVideoView(2468, 640, 480, 30));
+
+  ASSERT_TRUE(cricket::WriteJingleViewRequest(
+      "video1", view_request, &actual_view_elems, &error));
+
+  ASSERT_EQ(2U, actual_view_elems.size());
+  EXPECT_EQ(expected_view_elem1->Str(), actual_view_elems[0]->Str());
+  EXPECT_EQ(expected_view_elem2->Str(), actual_view_elems[1]->Str());
+
+  view_request.static_video_views.clear();
+  cricket::ParseError parse_error;
+  EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
+  ASSERT_TRUE(cricket::ParseJingleViewRequest(
+      action_elem.get(), &view_request, &parse_error));
+  EXPECT_EQ(2U, view_request.static_video_views.size());
+  EXPECT_EQ(1234U, view_request.static_video_views[0].ssrc);
+  EXPECT_EQ(640, view_request.static_video_views[0].width);
+  EXPECT_EQ(480, view_request.static_video_views[0].height);
+  EXPECT_EQ(30, view_request.static_video_views[0].framerate);
+  EXPECT_EQ(2468U, view_request.static_video_views[1].ssrc);
+}
+
+// Test deserializing bad view XML.
+TEST_F(MediaMessagesTest, ParseBadViewXml) {
+  talk_base::scoped_ptr<buzz::XmlElement> action_elem(
+      new buzz::XmlElement(QN_JINGLE));
+  buzz::XmlElement* view_elem =
+      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("not-an-ssrc"));
+  action_elem->AddElement(view_elem);
+
+  cricket::ViewRequest view_request;
+  cricket::ParseError parse_error;
+  ASSERT_FALSE(cricket::ParseJingleViewRequest(
+      action_elem.get(), &view_request, &parse_error));
+}
+
+
+// Test serializing/deserializing typical streams xml.
+TEST_F(MediaMessagesTest, StreamsToFromXml) {
+  talk_base::scoped_ptr<buzz::XmlElement> expected_streams_elem(
+      buzz::XmlElement::ForStr(
+          StreamsXml(
+              StreamXml("nick1", "name1", "101", "102",
+                        "semantics1", "type1", "display1"),
+              StreamXml("nick2", "name2", "201", "202",
+                        "semantics2", "type2", "display2"))));
+
+  std::vector<cricket::StreamParams> expected_streams;
+  expected_streams.push_back(CreateStream("nick1", "name1", 101U, 102U,
+                                          "semantics1", "type1", "display1"));
+  expected_streams.push_back(CreateStream("nick2", "name2", 201U, 202U,
+                                          "semantics2", "type2", "display2"));
+
+  talk_base::scoped_ptr<buzz::XmlElement> actual_desc_elem(
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+  cricket::WriteJingleStreams(expected_streams, actual_desc_elem.get());
+
+  const buzz::XmlElement* actual_streams_elem =
+      actual_desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+  ASSERT_TRUE(actual_streams_elem != NULL);
+  EXPECT_EQ(expected_streams_elem->Str(), actual_streams_elem->Str());
+
+  talk_base::scoped_ptr<buzz::XmlElement> expected_desc_elem(
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+  expected_desc_elem->AddElement(new buzz::XmlElement(
+      *expected_streams_elem));
+  std::vector<cricket::StreamParams> actual_streams;
+  cricket::ParseError parse_error;
+
+  EXPECT_TRUE(cricket::HasJingleStreams(expected_desc_elem.get()));
+  ASSERT_TRUE(cricket::ParseJingleStreams(
+      expected_desc_elem.get(), &actual_streams, &parse_error));
+  EXPECT_EQ(2U, actual_streams.size());
+  EXPECT_EQ(expected_streams[0], actual_streams[0]);
+  EXPECT_EQ(expected_streams[1], actual_streams[1]);
+}
+
+// Test deserializing bad streams xml.
+TEST_F(MediaMessagesTest, StreamsFromBadXml) {
+  talk_base::scoped_ptr<buzz::XmlElement> streams_elem(
+      buzz::XmlElement::ForStr(
+          StreamsXml(
+              StreamXml("nick1", "name1", "101", "not-an-ssrc",
+                        "semantics1", "type1", "display1"),
+              StreamXml("nick2", "name2", "202", "not-an-ssrc",
+                        "semantics2", "type2", "display2"))));
+  talk_base::scoped_ptr<buzz::XmlElement> desc_elem(
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+  desc_elem->AddElement(new buzz::XmlElement(*streams_elem));
+
+  std::vector<cricket::StreamParams> actual_streams;
+  cricket::ParseError parse_error;
+  ASSERT_FALSE(cricket::ParseJingleStreams(
+      desc_elem.get(), &actual_streams, &parse_error));
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/mediaprocessorinterface.h b/talk/session/phone/mediaprocessorinterface.h
new file mode 100644
index 0000000..2f8d02a
--- /dev/null
+++ b/talk/session/phone/mediaprocessorinterface.h
@@ -0,0 +1,84 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef TALK_SESSION_PHONE_MEDIAPROCESSORINTERFACE_H_
+#define TALK_SESSION_PHONE_MEDIAPROCESSORINTERFACE_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+
+enum MediaProcessorDirection {
+    MPD_RX = 1 << 0,
+    MPD_TX = 1 << 1,
+    MPD_RX_AND_TX = MPD_RX | MPD_TX,
+};
+
+struct AudioFrame {
+  AudioFrame()
+      : audio10ms(NULL),
+        length(0),
+        sampling_freq(8000),
+        is_stereo(false) {
+  }
+
+  AudioFrame(int16* audio, size_t audio_length, int sample_freq, bool stereo)
+      : audio10ms(audio),
+        length(audio_length),
+        sampling_freq(sample_freq),
+        is_stereo(stereo) {
+  }
+
+  int16* audio10ms;
+  size_t length;
+  int sampling_freq;
+  bool is_stereo;
+};
+
+class VoiceProcessor : public sigslot::has_slots<> {
+ public:
+  virtual ~VoiceProcessor() {}
+  // Contents of frame may be manipulated by the processor.
+  // The processed data is expected to be the same size as the
+  // original data
+  virtual void OnFrame(uint32 ssrc, AudioFrame* frame) = 0;
+};
+
+class VideoProcessor : public sigslot::has_slots<> {
+ public:
+  virtual ~VideoProcessor() {}
+  // Contents of frame may be manipulated by the processor.
+  // The processed data is expected to be the same size as the
+  // original data
+  virtual void OnFrame(uint32 ssrc, VideoFrame* frame) = 0;
+};
+
+}  // namespace cricket
+#endif  // TALK_SESSION_PHONE_MEDIAPROCESSORINTERFACE_H_
diff --git a/talk/session/phone/mediarecorder.cc b/talk/session/phone/mediarecorder.cc
new file mode 100644
index 0000000..7b06e48
--- /dev/null
+++ b/talk/session/phone/mediarecorder.cc
@@ -0,0 +1,225 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/mediarecorder.h"
+
+#include <limits.h>
+
+#include <string>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/rtpdump.h"
+
+
+namespace cricket {
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpSink.
+///////////////////////////////////////////////////////////////////////////
+RtpDumpSink::RtpDumpSink(const std::string& filename)
+    : max_size_(INT_MAX),
+      recording_(false),
+      packet_filter_(PF_NONE),
+      filename_(filename) {
+}
+
+RtpDumpSink::~RtpDumpSink() {}
+
+void RtpDumpSink::SetMaxSize(size_t size) {
+  talk_base::CritScope cs(&critical_section_);
+  max_size_ = size;
+}
+
+bool RtpDumpSink::Enable(bool enable) {
+  talk_base::CritScope cs(&critical_section_);
+
+  recording_ = enable;
+
+  // Create a file and the RTP writer if we have not done yet.
+  if (recording_ && !writer_.get()) {
+    stream_.reset(talk_base::Filesystem::OpenFile(
+        talk_base::Pathname(filename_), "wb"));
+    if (!stream_.get()) {
+      return false;
+    }
+    writer_.reset(new RtpDumpWriter(stream_.get()));
+    writer_->set_packet_filter(packet_filter_);
+  } else if (!recording_ && stream_.get()) {
+    stream_->Flush();
+  }
+  return true;
+}
+
+void RtpDumpSink::OnPacket(const void* data, size_t size, bool rtcp) {
+  talk_base::CritScope cs(&critical_section_);
+
+  if (recording_ && writer_.get()) {
+    size_t current_size;
+    if (writer_->GetDumpSize(&current_size) &&
+        current_size + RtpDumpPacket::kHeaderLength + size <= max_size_) {
+      if (!rtcp) {
+        writer_->WriteRtpPacket(data, size);
+      } else {
+        // TODO: Enable recording RTCP.
+      }
+    }
+  }
+}
+
+void RtpDumpSink::set_packet_filter(int filter) {
+  talk_base::CritScope cs(&critical_section_);
+  packet_filter_ = filter;
+  if (writer_.get()) {
+    writer_->set_packet_filter(packet_filter_);
+  }
+}
+
+void RtpDumpSink::Flush() {
+  talk_base::CritScope cs(&critical_section_);
+  if (stream_.get()) {
+    stream_->Flush();
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of MediaRecorder.
+///////////////////////////////////////////////////////////////////////////
+MediaRecorder::MediaRecorder() {}
+
+MediaRecorder::~MediaRecorder() {
+  talk_base::CritScope cs(&critical_section_);
+  std::map<BaseChannel*, SinkPair*>::iterator itr;
+  for (itr = sinks_.begin(); itr != sinks_.end(); ++itr) {
+    delete itr->second;
+  }
+}
+
+bool MediaRecorder::AddChannel(VoiceChannel* channel,
+                               const std::string& send_filename,
+                               const std::string& recv_filename,
+                               int filter) {
+  return InternalAddChannel(channel, false, send_filename, recv_filename,
+                            filter);
+}
+bool MediaRecorder::AddChannel(VideoChannel* channel,
+                               const std::string& send_filename,
+                               const std::string& recv_filename,
+                               int filter) {
+  return InternalAddChannel(channel, true, send_filename, recv_filename,
+                            filter);
+}
+
+bool MediaRecorder::InternalAddChannel(BaseChannel* channel,
+                                       bool video_channel,
+                                       const std::string& send_filename,
+                                       const std::string& recv_filename,
+                                       int filter) {
+  if (!channel) {
+    return false;
+  }
+
+  talk_base::CritScope cs(&critical_section_);
+  if (sinks_.end() != sinks_.find(channel)) {
+    return false;  // The channel was added already.
+  }
+
+  SinkPair* sink_pair = new SinkPair;
+  sink_pair->video_channel = video_channel;
+  sink_pair->filter = filter;
+  sink_pair->send_sink.reset(new RtpDumpSink(send_filename));
+  sink_pair->send_sink->set_packet_filter(filter);
+  sink_pair->recv_sink.reset(new RtpDumpSink(recv_filename));
+  sink_pair->recv_sink->set_packet_filter(filter);
+  sinks_[channel] = sink_pair;
+
+  return true;
+}
+
+void MediaRecorder::RemoveChannel(BaseChannel* channel) {
+  talk_base::CritScope cs(&critical_section_);
+  std::map<BaseChannel*, SinkPair*>::iterator itr = sinks_.find(channel);
+  if (sinks_.end() != itr) {
+    channel->UnregisterSendSink(itr->second->send_sink.get());
+    channel->UnregisterRecvSink(itr->second->recv_sink.get());
+    delete itr->second;
+    sinks_.erase(itr);
+  }
+}
+
+bool MediaRecorder::EnableChannel(
+    BaseChannel* channel, bool enable_send, bool enable_recv) {
+  talk_base::CritScope cs(&critical_section_);
+  std::map<BaseChannel*, SinkPair*>::iterator itr = sinks_.find(channel);
+  if (sinks_.end() == itr) {
+    return false;
+  }
+
+  SinkPair* sink_pair = itr->second;
+  RtpDumpSink* sink = sink_pair->send_sink.get();
+  sink->Enable(enable_send);
+  if (enable_send) {
+    channel->RegisterSendSink(sink, &RtpDumpSink::OnPacket);
+  } else {
+    channel->UnregisterSendSink(sink);
+  }
+
+  sink = sink_pair->recv_sink.get();
+  sink->Enable(enable_recv);
+  if (enable_recv) {
+    channel->RegisterRecvSink(sink, &RtpDumpSink::OnPacket);
+  } else {
+    channel->UnregisterRecvSink(sink);
+  }
+
+  if (sink_pair->video_channel &&
+      (sink_pair->filter & PF_RTPPACKET) == PF_RTPPACKET) {
+    // Request a full intra frame.
+    VideoChannel* video_channel = static_cast<VideoChannel*>(channel);
+    if (enable_send) {
+      video_channel->SendIntraFrame();
+    }
+    if (enable_recv) {
+      video_channel->RequestIntraFrame();
+    }
+  }
+
+  return true;
+}
+
+void MediaRecorder::FlushSinks() {
+  talk_base::CritScope cs(&critical_section_);
+  std::map<BaseChannel*, SinkPair*>::iterator itr;
+  for (itr = sinks_.begin(); itr != sinks_.end(); ++itr) {
+    itr->second->send_sink->Flush();
+    itr->second->recv_sink->Flush();
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/mediarecorder.h b/talk/session/phone/mediarecorder.h
new file mode 100644
index 0000000..fa77e6d
--- /dev/null
+++ b/talk/session/phone/mediarecorder.h
@@ -0,0 +1,117 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_MEDIARECORDER_H_
+#define TALK_SESSION_PHONE_MEDIARECORDER_H_
+
+#include <map>
+#include <string>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/mediasink.h"
+
+namespace talk_base {
+class Pathname;
+class FileStream;
+}
+
+namespace cricket {
+
+class BaseChannel;
+class VideoChannel;
+class VoiceChannel;
+class RtpDumpWriter;
+
+// RtpDumpSink implements MediaSinkInterface by dumping the RTP/RTCP packets to
+// a file.
+class RtpDumpSink : public MediaSinkInterface, public sigslot::has_slots<> {
+ public:
+  explicit RtpDumpSink(const std::string& filename);
+  virtual ~RtpDumpSink();
+
+  virtual void SetMaxSize(size_t size);
+  virtual bool Enable(bool enable);
+  virtual bool IsEnabled() const { return recording_; }
+  virtual void OnPacket(const void* data, size_t size, bool rtcp);
+  virtual void set_packet_filter(int filter);
+  int packet_filter() const { return packet_filter_; }
+  void Flush();
+
+ private:
+  size_t max_size_;
+  bool recording_;
+  int packet_filter_;
+  std::string filename_;
+  talk_base::scoped_ptr<talk_base::FileStream> stream_;
+  talk_base::scoped_ptr<RtpDumpWriter> writer_;
+  talk_base::CriticalSection critical_section_;
+
+  DISALLOW_COPY_AND_ASSIGN(RtpDumpSink);
+};
+
+class MediaRecorder {
+ public:
+  MediaRecorder();
+  virtual ~MediaRecorder();
+
+  bool AddChannel(VoiceChannel* channel,
+                  const std::string& send_filename,
+                  const std::string& recv_filename,
+                  int filter);
+  bool AddChannel(VideoChannel* channel,
+                  const std::string& send_filename,
+                  const std::string& recv_filename,
+                  int filter);
+  void RemoveChannel(BaseChannel* channel);
+  bool EnableChannel(BaseChannel* channel, bool enable_send, bool enable_recv);
+  void FlushSinks();
+
+ private:
+  struct SinkPair {
+    bool video_channel;
+    int filter;
+    talk_base::scoped_ptr<RtpDumpSink> send_sink;
+    talk_base::scoped_ptr<RtpDumpSink> recv_sink;
+  };
+
+  bool InternalAddChannel(BaseChannel* channel,
+                          bool video_channel,
+                          const std::string& send_filename,
+                          const std::string& recv_filename,
+                          int filter);
+
+  std::map<BaseChannel*, SinkPair*> sinks_;
+  talk_base::CriticalSection critical_section_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaRecorder);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_MEDIARECORDER_H_
diff --git a/talk/session/phone/mediarecorder_unittest.cc b/talk/session/phone/mediarecorder_unittest.cc
new file mode 100644
index 0000000..59d1afe
--- /dev/null
+++ b/talk/session/phone/mediarecorder_unittest.cc
@@ -0,0 +1,344 @@
+// libjingle
+// Copyright 2010 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <string>
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/mediarecorder.h"
+#include "talk/session/phone/rtpdump.h"
+#include "talk/session/phone/testutils.h"
+
+namespace cricket {
+
+/////////////////////////////////////////////////////////////////////////
+// Test RtpDumpSink
+/////////////////////////////////////////////////////////////////////////
+class RtpDumpSinkTest : public testing::Test {
+ public:
+  virtual void SetUp() {
+    EXPECT_TRUE(talk_base::Filesystem::GetTemporaryFolder(path_, true, NULL));
+    path_.SetFilename("sink-test.rtpdump");
+    sink_.reset(new RtpDumpSink(path_.pathname()));
+
+    for (int i = 0; i < ARRAY_SIZE(rtp_buf_); ++i) {
+      RtpTestUtility::kTestRawRtpPackets[i].WriteToByteBuffer(
+          RtpTestUtility::kDefaultSsrc, &rtp_buf_[i]);
+    }
+  }
+
+  virtual void TearDown() {
+    stream_.reset();
+    EXPECT_TRUE(talk_base::Filesystem::DeleteFile(path_));
+  }
+
+ protected:
+  void OnRtpPacket(const RawRtpPacket& raw) {
+    talk_base::ByteBuffer buf;
+    raw.WriteToByteBuffer(RtpTestUtility::kDefaultSsrc, &buf);
+    sink_->OnPacket(buf.Data(), buf.Length(), false);
+  }
+
+  talk_base::StreamResult ReadPacket(RtpDumpPacket* packet) {
+    if (!stream_.get()) {
+      sink_.reset();  // This will close the file. So we can read it.
+      stream_.reset(talk_base::Filesystem::OpenFile(path_, "rb"));
+      reader_.reset(new RtpDumpReader(stream_.get()));
+    }
+    return reader_->ReadPacket(packet);
+  }
+
+  talk_base::Pathname path_;
+  talk_base::scoped_ptr<RtpDumpSink> sink_;
+  talk_base::ByteBuffer rtp_buf_[3];
+  talk_base::scoped_ptr<talk_base::StreamInterface> stream_;
+  talk_base::scoped_ptr<RtpDumpReader> reader_;
+};
+
+TEST_F(RtpDumpSinkTest, TestRtpDumpSink) {
+  // By default, the sink is disabled. The 1st packet is not written.
+  EXPECT_FALSE(sink_->IsEnabled());
+  sink_->set_packet_filter(PF_ALL);
+  OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[0]);
+
+  // Enable the sink. The 2nd packet is written.
+  EXPECT_TRUE(sink_->Enable(true));
+  EXPECT_TRUE(sink_->IsEnabled());
+  EXPECT_TRUE(talk_base::Filesystem::IsFile(path_.pathname()));
+  OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[1]);
+
+  // Disable the sink. The 3rd packet is not written.
+  EXPECT_TRUE(sink_->Enable(false));
+  EXPECT_FALSE(sink_->IsEnabled());
+  OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[2]);
+
+  // Read the recorded file and verify it contains only the 2nd packet.
+  RtpDumpPacket packet;
+  EXPECT_EQ(talk_base::SR_SUCCESS, ReadPacket(&packet));
+  EXPECT_TRUE(RtpTestUtility::VerifyPacket(
+      &packet, &RtpTestUtility::kTestRawRtpPackets[1], false));
+  EXPECT_EQ(talk_base::SR_EOS, ReadPacket(&packet));
+}
+
+TEST_F(RtpDumpSinkTest, TestRtpDumpSinkMaxSize) {
+  EXPECT_TRUE(sink_->Enable(true));
+  sink_->set_packet_filter(PF_ALL);
+  sink_->SetMaxSize(strlen(RtpDumpFileHeader::kFirstLine) +
+                    RtpDumpFileHeader::kHeaderLength +
+                    RtpDumpPacket::kHeaderLength +
+                    RtpTestUtility::kTestRawRtpPackets[0].size());
+  OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[0]);
+
+  // Exceed the limit size: the 2nd and 3rd packets are not written.
+  OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[1]);
+  OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[2]);
+
+  // Read the recorded file and verify that it contains only the first packet.
+  RtpDumpPacket packet;
+  EXPECT_EQ(talk_base::SR_SUCCESS, ReadPacket(&packet));
+  EXPECT_TRUE(RtpTestUtility::VerifyPacket(
+      &packet, &RtpTestUtility::kTestRawRtpPackets[0], false));
+  EXPECT_EQ(talk_base::SR_EOS, ReadPacket(&packet));
+}
+
+TEST_F(RtpDumpSinkTest, TestRtpDumpSinkFilter) {
+  // The default filter is PF_NONE.
+  EXPECT_EQ(PF_NONE, sink_->packet_filter());
+
+  // Set to PF_RTPHEADER before enable.
+  sink_->set_packet_filter(PF_RTPHEADER);
+  EXPECT_EQ(PF_RTPHEADER, sink_->packet_filter());
+  EXPECT_TRUE(sink_->Enable(true));
+  // We dump only the header of the first packet.
+  OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[0]);
+
+  // Set the filter to PF_RTPPACKET. We dump all the second packet.
+  sink_->set_packet_filter(PF_RTPPACKET);
+  EXPECT_EQ(PF_RTPPACKET, sink_->packet_filter());
+  OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[1]);
+
+  // Set the filter to PF_NONE. We do not dump the third packet.
+  sink_->set_packet_filter(PF_NONE);
+  EXPECT_EQ(PF_NONE, sink_->packet_filter());
+  OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[2]);
+
+  // Read the recorded file and verify the header of the first packet and
+  // the whole packet for the second packet.
+  RtpDumpPacket packet;
+  EXPECT_EQ(talk_base::SR_SUCCESS, ReadPacket(&packet));
+  EXPECT_TRUE(RtpTestUtility::VerifyPacket(
+      &packet, &RtpTestUtility::kTestRawRtpPackets[0], true));
+  EXPECT_EQ(talk_base::SR_SUCCESS, ReadPacket(&packet));
+  EXPECT_TRUE(RtpTestUtility::VerifyPacket(
+      &packet, &RtpTestUtility::kTestRawRtpPackets[1], false));
+  EXPECT_EQ(talk_base::SR_EOS, ReadPacket(&packet));
+}
+
+/////////////////////////////////////////////////////////////////////////
+// Test MediaRecorder
+/////////////////////////////////////////////////////////////////////////
+void TestMediaRecorder(BaseChannel* channel,
+                       FakeVideoMediaChannel* video_media_channel,
+                       int filter) {
+  // Create media recorder.
+  talk_base::scoped_ptr<MediaRecorder> recorder(new MediaRecorder);
+  // Fail to EnableChannel before AddChannel.
+  EXPECT_FALSE(recorder->EnableChannel(channel, true, true));
+  EXPECT_FALSE(channel->HasSendSinks());
+  EXPECT_FALSE(channel->HasRecvSinks());
+
+  // Add the channel to the recorder.
+  talk_base::Pathname path;
+  EXPECT_TRUE(talk_base::Filesystem::GetTemporaryFolder(path, true, NULL));
+  path.SetFilename("send.rtpdump");
+  std::string send_file = path.pathname();
+  path.SetFilename("recv.rtpdump");
+  std::string recv_file = path.pathname();
+  if (video_media_channel) {
+    EXPECT_TRUE(recorder->AddChannel(static_cast<VideoChannel*>(channel),
+                                     send_file, recv_file, filter));
+  } else {
+    EXPECT_TRUE(recorder->AddChannel(static_cast<VoiceChannel*>(channel),
+                                     send_file, recv_file, filter));
+  }
+
+  // Enable recording only the sent media.
+  EXPECT_TRUE(recorder->EnableChannel(channel, true, false));
+  EXPECT_TRUE(channel->HasSendSinks());
+  EXPECT_FALSE(channel->HasRecvSinks());
+  if (video_media_channel) {
+    EXPECT_TRUE_WAIT(video_media_channel->sent_intra_frame(), 100);
+  }
+
+  // Enable recording only the received meida.
+  EXPECT_TRUE(recorder->EnableChannel(channel, false, true));
+  EXPECT_FALSE(channel->HasSendSinks());
+  EXPECT_TRUE(channel->HasRecvSinks());
+  if (video_media_channel) {
+    EXPECT_TRUE(video_media_channel->requested_intra_frame());
+  }
+
+  // Enable recording both the sent and the received video.
+  EXPECT_TRUE(recorder->EnableChannel(channel, true, true));
+  EXPECT_TRUE(channel->HasSendSinks());
+  EXPECT_TRUE(channel->HasRecvSinks());
+
+  // Enable recording only headers.
+  if (video_media_channel) {
+    video_media_channel->set_sent_intra_frame(false);
+    video_media_channel->set_requested_intra_frame(false);
+  }
+  EXPECT_TRUE(recorder->EnableChannel(channel, true, true));
+  EXPECT_TRUE(channel->HasSendSinks());
+  EXPECT_TRUE(channel->HasRecvSinks());
+  if (video_media_channel) {
+    if ((filter & PF_RTPPACKET) == PF_RTPPACKET) {
+      // If record the whole RTP packet, trigers FIR.
+      EXPECT_TRUE(video_media_channel->requested_intra_frame());
+      EXPECT_TRUE(video_media_channel->sent_intra_frame());
+    } else {
+      // If record only the RTP header, does not triger FIR.
+      EXPECT_FALSE(video_media_channel->requested_intra_frame());
+      EXPECT_FALSE(video_media_channel->sent_intra_frame());
+    }
+  }
+
+  // Remove the voice channel from the recorder.
+  recorder->RemoveChannel(channel);
+  EXPECT_FALSE(channel->HasSendSinks());
+  EXPECT_FALSE(channel->HasRecvSinks());
+
+  // Delete all files.
+  recorder.reset();
+  EXPECT_TRUE(talk_base::Filesystem::DeleteFile(send_file));
+  EXPECT_TRUE(talk_base::Filesystem::DeleteFile(recv_file));
+}
+
+// Fisrt start recording header and then start recording media. Verify that
+// differnt files are created for header and media.
+void TestRecordHeaderAndMedia(BaseChannel* channel,
+                              FakeVideoMediaChannel* video_media_channel) {
+  // Create RTP header recorder.
+  talk_base::scoped_ptr<MediaRecorder> header_recorder(new MediaRecorder);
+
+  talk_base::Pathname path;
+  EXPECT_TRUE(talk_base::Filesystem::GetTemporaryFolder(path, true, NULL));
+  path.SetFilename("send-header.rtpdump");
+  std::string send_header_file = path.pathname();
+  path.SetFilename("recv-header.rtpdump");
+  std::string recv_header_file = path.pathname();
+  if (video_media_channel) {
+    EXPECT_TRUE(header_recorder->AddChannel(
+        static_cast<VideoChannel*>(channel),
+        send_header_file, recv_header_file, PF_RTPHEADER));
+  } else {
+    EXPECT_TRUE(header_recorder->AddChannel(
+        static_cast<VoiceChannel*>(channel),
+        send_header_file, recv_header_file, PF_RTPHEADER));
+  }
+
+  // Enable recording both sent and received.
+  EXPECT_TRUE(header_recorder->EnableChannel(channel, true, true));
+  EXPECT_TRUE(channel->HasSendSinks());
+  EXPECT_TRUE(channel->HasRecvSinks());
+  if (video_media_channel) {
+    EXPECT_FALSE(video_media_channel->sent_intra_frame());
+    EXPECT_FALSE(video_media_channel->requested_intra_frame());
+  }
+
+  // Verify that header files are created.
+  EXPECT_TRUE(talk_base::Filesystem::IsFile(send_header_file));
+  EXPECT_TRUE(talk_base::Filesystem::IsFile(recv_header_file));
+
+  // Create RTP header recorder.
+  talk_base::scoped_ptr<MediaRecorder> recorder(new MediaRecorder);
+  path.SetFilename("send.rtpdump");
+  std::string send_file = path.pathname();
+  path.SetFilename("recv.rtpdump");
+  std::string recv_file = path.pathname();
+  if (video_media_channel) {
+    EXPECT_TRUE(recorder->AddChannel(
+        static_cast<VideoChannel*>(channel),
+        send_file, recv_file, PF_RTPPACKET));
+  } else {
+    EXPECT_TRUE(recorder->AddChannel(
+        static_cast<VoiceChannel*>(channel),
+        send_file, recv_file, PF_RTPPACKET));
+  }
+
+  // Enable recording both sent and received.
+  EXPECT_TRUE(recorder->EnableChannel(channel, true, true));
+  EXPECT_TRUE(channel->HasSendSinks());
+  EXPECT_TRUE(channel->HasRecvSinks());
+  if (video_media_channel) {
+    EXPECT_TRUE_WAIT(video_media_channel->sent_intra_frame(), 100);
+    EXPECT_TRUE(video_media_channel->requested_intra_frame());
+  }
+
+  // Verify that media files are created.
+  EXPECT_TRUE(talk_base::Filesystem::IsFile(send_file));
+  EXPECT_TRUE(talk_base::Filesystem::IsFile(recv_file));
+
+  // Delete all files.
+  header_recorder.reset();
+  recorder.reset();
+  EXPECT_TRUE(talk_base::Filesystem::DeleteFile(send_header_file));
+  EXPECT_TRUE(talk_base::Filesystem::DeleteFile(recv_header_file));
+  EXPECT_TRUE(talk_base::Filesystem::DeleteFile(send_file));
+  EXPECT_TRUE(talk_base::Filesystem::DeleteFile(recv_file));
+}
+
+TEST(MediaRecorderTest, TestMediaRecorderVoiceChannel) {
+  // Create the voice channel.
+  cricket::FakeSession session;
+  cricket::FakeMediaEngine media_engine;
+  VoiceChannel channel(talk_base::Thread::Current(), &media_engine,
+                       new FakeVoiceMediaChannel(NULL), &session, "", false);
+  EXPECT_TRUE(channel.Init());
+  TestMediaRecorder(&channel, NULL, PF_RTPPACKET);
+  TestMediaRecorder(&channel, NULL, PF_RTPHEADER);
+  TestRecordHeaderAndMedia(&channel, NULL);
+}
+
+TEST(MediaRecorderTest, TestMediaRecorderVideoChannel) {
+  // Create the video channel.
+  cricket::FakeSession session;
+  cricket::FakeMediaEngine media_engine;
+  FakeVideoMediaChannel* media_channel = new FakeVideoMediaChannel(NULL);
+  VideoChannel channel(talk_base::Thread::Current(), &media_engine,
+                       media_channel, &session, "", false, NULL);
+  EXPECT_TRUE(channel.Init());
+  TestMediaRecorder(&channel, media_channel, PF_RTPPACKET);
+  TestMediaRecorder(&channel, media_channel, PF_RTPHEADER);
+  TestRecordHeaderAndMedia(&channel, media_channel);
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/mediasession.cc b/talk/session/phone/mediasession.cc
index b423708..ed4eaa7 100644
--- a/talk/session/phone/mediasession.cc
+++ b/talk/session/phone/mediasession.cc
@@ -29,6 +29,7 @@
 
 #include "talk/base/helpers.h"
 #include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
 #include "talk/p2p/base/constants.h"
 #include "talk/session/phone/channelmanager.h"
 #include "talk/session/phone/cryptoparams.h"
@@ -41,6 +42,8 @@
 
 namespace cricket {
 
+using talk_base::scoped_ptr;
+
 static bool CreateCryptoParams(int tag, const std::string& cipher,
                                CryptoParams *out) {
   std::string key;
@@ -102,40 +105,215 @@
   return false;
 }
 
+static const StreamParams* FindFirstStreamParamsByCname(
+    const StreamParamsVec& params_vec,
+    const std::string& cname) {
+  for (StreamParamsVec::const_iterator it = params_vec.begin();
+       it != params_vec.end(); ++it) {
+    if (cname == it->cname)
+      return &*it;
+  }
+  return NULL;
+}
+
+// Generates a new CNAME or the CNAME of an already existing StreamParams
+// if a StreamParams exist for another Stream in streams with sync_label
+// sync_label.
+static bool GenerateCname(const StreamParamsVec& params_vec,
+                          const MediaSessionOptions::Streams& streams,
+                          const std::string& synch_label,
+                          std::string* cname) {
+  ASSERT(cname != NULL);
+  if (!cname)
+    return false;
+
+  // Check if a CNAME exist for any of the other synched streams.
+  for (MediaSessionOptions::Streams::const_iterator stream_it = streams.begin();
+       stream_it != streams.end() ; ++stream_it) {
+    if (synch_label != stream_it->sync_label)
+      continue;
+
+    StreamParams param;
+    // nick is empty for StreamParams generated using
+    // MediaSessionDescriptionFactory.
+    if (GetStreamByNickAndName(params_vec, "", stream_it->name,
+                               &param)) {
+      *cname = param.cname;
+      return true;
+    }
+  }
+  // No other stream seems to exist that we should sync with.
+  // Generate a random string for the RTCP CNAME, as stated in RFC 6222.
+  // This string is only used for synchronization, and therefore is opaque.
+  do {
+    if (!talk_base::CreateRandomString(16, cname)) {
+      ASSERT(false);
+      return false;
+    }
+  } while (FindFirstStreamParamsByCname(params_vec, *cname));
+
+  return true;
+}
+
+// Generate a new SSRC and make sure it does not exist in params_vec.
+static uint32 GenerateSsrc(const StreamParamsVec& params_vec) {
+  uint32 ssrc = 0;
+  do {
+    ssrc = talk_base::CreateRandomNonZeroId();
+  } while (GetStreamBySsrc(params_vec, ssrc, NULL));
+  return ssrc;
+}
+
+// Finds all StreamParams of all media types and attach them to stream_params.
+static void GetCurrentStreamParams(const SessionDescription* sdesc,
+                                   StreamParamsVec* stream_params) {
+  if (!sdesc)
+    return;
+
+  const ContentInfos& contents = sdesc->contents();
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); content++) {
+    if (!IsAudioContent(&*content) && !IsVideoContent(&*content))
+      continue;
+    const MediaContentDescription* media =
+        static_cast<const MediaContentDescription*>(
+            content->description);
+    const StreamParamsVec& streams = media->streams();
+    for (StreamParamsVec::const_iterator it = streams.begin();
+         it != streams.end(); ++it) {
+      stream_params->push_back(*it);
+    }
+  }
+}
+
+// Adds a StreamParams for each Stream in Streams with media type
+// media_type to content_description.
+// current_parms - All currently known StreamParams of any media type.
+static bool AddStreamParams(
+    MediaType media_type,
+    const MediaSessionOptions::Streams& streams,
+    StreamParamsVec* current_params,
+    MediaContentDescription* content_description) {
+  for (MediaSessionOptions::Streams::const_iterator stream_it = streams.begin();
+       stream_it != streams.end(); ++stream_it) {
+    if (stream_it->type != media_type)
+      continue;  // Wrong media type.
+
+    StreamParams param;
+    // nick is empty for StreamParams generated using
+    // MediaSessionDescriptionFactory.
+    if (!GetStreamByNickAndName(*current_params, "", stream_it->name, &param)) {
+      // This is a new stream.
+      // Get a CNAME. Either new or same as one of the other synched streams.
+      std::string cname;
+      if (!GenerateCname(*current_params, streams, stream_it->sync_label,
+                         &cname)) {
+        return false;
+      }
+      uint32 ssrc = GenerateSsrc(*current_params);
+      // TODO: Generate the more complex types of stream_params.
+
+      StreamParams stream_param;
+      stream_param.name = stream_it->name;
+      stream_param.ssrcs.push_back(ssrc);
+      stream_param.cname = cname;
+      stream_param.sync_label = stream_it->sync_label;
+      content_description->AddStream(stream_param);
+
+      // Store the new StreamParams in current_params.
+      // This is necessary so that we can use the CNAME for other media types.
+      current_params->push_back(stream_param);
+    } else {
+      content_description->AddStream(param);
+    }
+  }
+  return true;
+}
+
+void MediaSessionOptions::AddStream(MediaType type,
+                                    const std::string& name,
+                                    const std::string& sync_label) {
+  streams.push_back(Stream(type, name, sync_label));
+
+  if (type == MEDIA_TYPE_VIDEO)
+    has_video = true;
+  else if (type == MEDIA_TYPE_AUDIO)
+    has_audio = true;
+}
+
+void MediaSessionOptions::RemoveStream(MediaType type,
+                                       const std::string& name) {
+  Streams::iterator stream_it = streams.begin();
+  for (; stream_it != streams.end(); ++stream_it) {
+    if (stream_it->type == type && stream_it->name == name) {
+      streams.erase(stream_it);
+      return;
+    }
+  }
+  ASSERT(false);
+}
+
 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory()
-    : secure_(SEC_DISABLED) {
+    : secure_(SEC_DISABLED),
+      add_legacy_(true) {
 }
 
 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
     ChannelManager* channel_manager)
-    : secure_(SEC_DISABLED) {
+    : secure_(SEC_DISABLED),
+      add_legacy_(true) {
   channel_manager->GetSupportedAudioCodecs(&audio_codecs_);
   channel_manager->GetSupportedVideoCodecs(&video_codecs_);
 }
 
 SessionDescription* MediaSessionDescriptionFactory::CreateOffer(
-    const MediaSessionOptions& options) {
-  SessionDescription* offer = new SessionDescription();
+    const MediaSessionOptions& options,
+    const SessionDescription* current_description) {
+  scoped_ptr<SessionDescription> offer(new SessionDescription());
+
+  StreamParamsVec current_params;
+  GetCurrentStreamParams(current_description, &current_params);
 
   if (options.has_audio) {
-    AudioContentDescription* audio = new AudioContentDescription();
+    scoped_ptr<AudioContentDescription> audio(new AudioContentDescription());
     for (AudioCodecs::const_iterator codec = audio_codecs_.begin();
          codec != audio_codecs_.end(); ++codec) {
       audio->AddCodec(*codec);
     }
     audio->SortCodecs();
-    audio->set_ssrc(talk_base::CreateRandomNonZeroId());
-    audio->set_rtcp_mux(true);
+    if (!AddStreamParams(MEDIA_TYPE_AUDIO, options.streams, &current_params,
+                         audio.get())) {
+      return NULL;  // Abort, something went seriously wrong.
+    }
+
+    if (options.streams.empty() && add_legacy_) {
+      // TODO: Remove this legacy stream when all apps use StreamParams.
+      audio->AddLegacyStream(talk_base::CreateRandomNonZeroId());
+    }
+    audio->set_multistream(options.is_muc);
+    audio->set_rtcp_mux(options.rtcp_mux_enabled);
     audio->set_lang(lang_);
 
     if (secure() != SEC_DISABLED) {
       CryptoParamsVec audio_cryptos;
-      if (GetSupportedAudioCryptos(&audio_cryptos)) {
-        for (CryptoParamsVec::const_iterator crypto = audio_cryptos.begin();
-             crypto != audio_cryptos.end(); ++crypto) {
-          audio->AddCrypto(*crypto);
+      if (current_description) {
+        // Copy crypto parameters from the previous offer.
+        const ContentInfo* info =
+            GetFirstAudioContent(current_description);
+        if (info) {
+          const AudioContentDescription* desc =
+              static_cast<const AudioContentDescription*>(info->description);
+          audio_cryptos = desc->cryptos();
         }
       }
+      if (audio_cryptos.empty())
+        GetSupportedAudioCryptos(&audio_cryptos);  // Generate new cryptos.
+
+      for (CryptoParamsVec::const_iterator crypto = audio_cryptos.begin();
+           crypto != audio_cryptos.end(); ++crypto) {
+        audio->AddCrypto(*crypto);
+      }
+
       if (secure() == SEC_REQUIRED) {
         if (audio->cryptos().empty()) {
           return NULL;  // Abort, crypto required but none found.
@@ -144,30 +322,47 @@
       }
     }
 
-    offer->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio);
+    offer->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio.release());
   }
 
   // add video codecs, if this is a video call
   if (options.has_video) {
-    VideoContentDescription* video = new VideoContentDescription();
+    scoped_ptr<VideoContentDescription> video(new VideoContentDescription());
     for (VideoCodecs::const_iterator codec = video_codecs_.begin();
          codec != video_codecs_.end(); ++codec) {
       video->AddCodec(*codec);
     }
 
     video->SortCodecs();
-    video->set_ssrc(talk_base::CreateRandomNonZeroId());
+    if (!AddStreamParams(MEDIA_TYPE_VIDEO, options.streams, &current_params,
+                         video.get())) {
+      return NULL;  // Abort, something went seriously wrong.
+    }
+
+    if (options.streams.empty() && add_legacy_) {
+      // TODO: Remove this legacy stream when all apps use StreamParams.
+      video->AddLegacyStream(talk_base::CreateRandomNonZeroId());
+    }
+    video->set_multistream(options.is_muc);
     video->set_bandwidth(options.video_bandwidth);
-    video->set_rtcp_mux(true);
+    video->set_rtcp_mux(options.rtcp_mux_enabled);
 
     if (secure() != SEC_DISABLED) {
       CryptoParamsVec video_cryptos;
-      if (GetSupportedVideoCryptos(&video_cryptos)) {
-        for (CryptoParamsVec::const_iterator crypto = video_cryptos.begin();
-             crypto != video_cryptos.end(); ++crypto) {
-          video->AddCrypto(*crypto);
+      if (current_description) {
+        // Copy crypto parameters from the previous offer.
+        const VideoContentDescription* desc =
+            GetFirstVideoContentDescription(current_description);
+        if (desc) {
+          video_cryptos = desc->cryptos();
         }
       }
+      if (video_cryptos.empty())
+        GetSupportedVideoCryptos(&video_cryptos);  // Generate new crypto.
+      for (CryptoParamsVec::const_iterator crypto = video_cryptos.begin();
+           crypto != video_cryptos.end(); ++crypto) {
+        video->AddCrypto(*crypto);
+      }
       if (secure() == SEC_REQUIRED) {
         if (video->cryptos().empty()) {
           return NULL;  // Abort, crypto required but none found.
@@ -176,24 +371,29 @@
       }
     }
 
-    offer->AddContent(CN_VIDEO, NS_JINGLE_RTP, video);
+    offer->AddContent(CN_VIDEO, NS_JINGLE_RTP, video.release());
   }
 
-  return offer;
+  return offer.release();
 }
 
 SessionDescription* MediaSessionDescriptionFactory::CreateAnswer(
-    const SessionDescription* offer, const MediaSessionOptions& options) {
+    const SessionDescription* offer, const MediaSessionOptions& options,
+    const SessionDescription* current_description) {
   // The answer contains the intersection of the codecs in the offer with the
   // codecs we support, ordered by our local preference. As indicated by
   // XEP-0167, we retain the same payload ids from the offer in the answer.
-  SessionDescription* accept = new SessionDescription();
+  scoped_ptr<SessionDescription> accept(new SessionDescription());
+
+  StreamParamsVec current_params;
+  GetCurrentStreamParams(current_description, &current_params);
 
   const ContentInfo* audio_content = GetFirstAudioContent(offer);
   if (audio_content && options.has_audio) {
     const AudioContentDescription* audio_offer =
         static_cast<const AudioContentDescription*>(audio_content->description);
-    AudioContentDescription* audio_accept = new AudioContentDescription();
+    scoped_ptr<AudioContentDescription> audio_accept(
+        new AudioContentDescription());
     for (AudioCodecs::const_iterator ours = audio_codecs_.begin();
         ours != audio_codecs_.end(); ++ours) {
       for (AudioCodecs::const_iterator theirs = audio_offer->codecs().begin();
@@ -207,13 +407,40 @@
     }
 
     audio_accept->SortCodecs();
-    audio_accept->set_ssrc(talk_base::CreateRandomNonZeroId());
-    audio_accept->set_rtcp_mux(audio_offer->rtcp_mux());
+    if (!AddStreamParams(MEDIA_TYPE_AUDIO, options.streams, &current_params,
+                         audio_accept.get())) {
+      return NULL;  // Abort, something went seriously wrong.
+    }
+
+    if (options.streams.empty() && add_legacy_) {
+      // TODO: Remove this legacy stream when all apps use StreamParams.
+      audio_accept->AddLegacyStream(talk_base::CreateRandomNonZeroId());
+    }
+    audio_accept->set_rtcp_mux(
+        options.rtcp_mux_enabled && audio_offer->rtcp_mux());
 
     if (secure() != SEC_DISABLED) {
       CryptoParams crypto;
 
       if (SelectCrypto(audio_offer, &crypto)) {
+        if (current_description) {
+          // Check if this crypto already exist in the previous
+          // session description. Use it in that case.
+          const ContentInfo* info =
+              GetFirstAudioContent(current_description);
+          if (info) {
+            const AudioContentDescription* desc =
+                static_cast<const AudioContentDescription*>(info->description);
+            const CryptoParamsVec& cryptos = desc->cryptos();
+            for (CryptoParamsVec::const_iterator it = cryptos.begin();
+                it != cryptos.end(); ++it) {
+              if (crypto.Matches(*it)) {
+                crypto = *it;
+                break;
+              }
+            }
+          }
+        }
         audio_accept->AddCrypto(crypto);
       }
     }
@@ -222,7 +449,8 @@
         (audio_offer->crypto_required() || secure() == SEC_REQUIRED)) {
       return NULL;  // Fails the session setup.
     }
-    accept->AddContent(audio_content->name, audio_content->type, audio_accept);
+    accept->AddContent(audio_content->name, audio_content->type,
+                       audio_accept.release());
   } else {
     LOG(LS_INFO) << "Audio is not supported in answer";
   }
@@ -231,7 +459,8 @@
   if (video_content && options.has_video) {
     const VideoContentDescription* video_offer =
         static_cast<const VideoContentDescription*>(video_content->description);
-    VideoContentDescription* video_accept = new VideoContentDescription();
+    scoped_ptr<VideoContentDescription> video_accept(
+        new VideoContentDescription());
     for (VideoCodecs::const_iterator ours = video_codecs_.begin();
         ours != video_codecs_.end(); ++ours) {
       for (VideoCodecs::const_iterator theirs = video_offer->codecs().begin();
@@ -243,16 +472,40 @@
         }
       }
     }
+    if (!AddStreamParams(MEDIA_TYPE_VIDEO, options.streams, &current_params,
+                         video_accept.get())) {
+      return NULL;  // Abort, something went seriously wrong.
+    }
 
-    video_accept->set_ssrc(talk_base::CreateRandomNonZeroId());
+    if (options.streams.empty() && add_legacy_) {
+      // TODO: Remove this legacy stream when all apps use StreamParams.
+      video_accept->AddLegacyStream(talk_base::CreateRandomNonZeroId());
+    }
     video_accept->set_bandwidth(options.video_bandwidth);
-    video_accept->set_rtcp_mux(video_offer->rtcp_mux());
+    video_accept->set_rtcp_mux(
+        options.rtcp_mux_enabled && video_offer->rtcp_mux());
     video_accept->SortCodecs();
 
     if (secure() != SEC_DISABLED) {
       CryptoParams crypto;
 
       if (SelectCrypto(video_offer, &crypto)) {
+        if (current_description) {
+          // Check if this crypto already exist in the previous
+          // session description. Use it in that case.
+          const VideoContentDescription* desc =
+              GetFirstVideoContentDescription(current_description);
+          if (desc) {
+            const CryptoParamsVec& cryptos = desc->cryptos();
+            for (CryptoParamsVec::const_iterator it = cryptos.begin();
+                 it != cryptos.end(); ++it) {
+              if (crypto.Matches(*it)) {
+                crypto = *it;
+                break;
+              }
+            }
+          }
+        }
         video_accept->AddCrypto(crypto);
       }
     }
@@ -261,12 +514,12 @@
         (video_offer->crypto_required() || secure() == SEC_REQUIRED)) {
       return NULL;  // Fails the session setup.
     }
-    accept->AddContent(video_content->name, video_content->type, video_accept);
+    accept->AddContent(video_content->name, video_content->type,
+                       video_accept.release());
   } else {
     LOG(LS_INFO) << "Video is not supported in answer";
   }
-
-  return accept;
+  return accept.release();
 }
 
 static bool IsMediaContent(const ContentInfo* content, MediaType media_type) {
@@ -287,12 +540,8 @@
   return IsMediaContent(content, MEDIA_TYPE_VIDEO);
 }
 
-static const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc,
+static const ContentInfo* GetFirstMediaContent(const ContentInfos& contents,
                                                MediaType media_type) {
-  if (sdesc == NULL)
-    return NULL;
-
-  const ContentInfos& contents = sdesc->contents();
   for (ContentInfos::const_iterator content = contents.begin();
        content != contents.end(); content++) {
     if (IsMediaContent(&*content, media_type)) {
@@ -302,6 +551,22 @@
   return NULL;
 }
 
+const ContentInfo* GetFirstAudioContent(const ContentInfos& contents) {
+  return GetFirstMediaContent(contents, MEDIA_TYPE_AUDIO);
+}
+
+const ContentInfo* GetFirstVideoContent(const ContentInfos& contents) {
+  return GetFirstMediaContent(contents, MEDIA_TYPE_VIDEO);
+}
+
+static const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc,
+                                               MediaType media_type) {
+  if (sdesc == NULL)
+    return NULL;
+
+  return GetFirstMediaContent(sdesc->contents(), media_type);
+}
+
 const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc) {
   return GetFirstMediaContent(sdesc, MEDIA_TYPE_AUDIO);
 }
@@ -310,4 +575,18 @@
   return GetFirstMediaContent(sdesc, MEDIA_TYPE_VIDEO);
 }
 
+const AudioContentDescription* GetFirstAudioContentDescription(
+    const SessionDescription* sdesc) {
+  const ContentInfo* content = GetFirstAudioContent(sdesc);
+  const ContentDescription* description = content ? content->description : NULL;
+  return static_cast<const AudioContentDescription*>(description);
+}
+
+const VideoContentDescription* GetFirstVideoContentDescription(
+    const SessionDescription* sdesc) {
+  const ContentInfo* content = GetFirstVideoContent(sdesc);
+  const ContentDescription* description = content ? content->description : NULL;
+  return static_cast<const VideoContentDescription*>(description);
+}
+
 }  // namespace cricket
diff --git a/talk/session/phone/mediasession.h b/talk/session/phone/mediasession.h
index 4e2a8fb..718f013 100644
--- a/talk/session/phone/mediasession.h
+++ b/talk/session/phone/mediasession.h
@@ -37,6 +37,7 @@
 #include "talk/session/phone/codec.h"
 #include "talk/session/phone/cryptoparams.h"
 #include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/streamparams.h"
 #include "talk/p2p/base/sessiondescription.h"
 
 namespace cricket {
@@ -64,6 +65,11 @@
   SEC_REQUIRED
 };
 
+enum MediaType {
+  MEDIA_TYPE_AUDIO,
+  MEDIA_TYPE_VIDEO
+};
+
 // Options to control how session descriptions are generated.
 const int kAutoBandwidth = -1;
 struct MediaSessionOptions {
@@ -71,41 +77,55 @@
       has_audio(true),  // Audio enabled by default.
       has_video(false),
       is_muc(false),
+      rtcp_mux_enabled(true),
       video_bandwidth(kAutoBandwidth) {
   }
 
+  // Add a stream with MediaType type and id name.
+  // All streams with the same sync_label will get the same CNAME.
+  // All names must be unique.
+  void AddStream(MediaType type,
+                 const std::string& name,
+                 const std::string& sync_label);
+  void RemoveStream(MediaType type, const std::string& name);
+
   bool has_audio;
   bool has_video;
   bool is_muc;
+  bool rtcp_mux_enabled;
   // bps. -1 == auto.
   int video_bandwidth;
-};
 
-enum MediaType {
-  MEDIA_TYPE_AUDIO,
-  MEDIA_TYPE_VIDEO
+  struct Stream {
+    Stream(MediaType type,
+           const std::string& name,
+           const std::string& sync_label)
+        : type(type), name(name), sync_label(sync_label) {
+    }
+    MediaType type;
+    std::string name;
+    std::string sync_label;
+  };
+
+  typedef std::vector<Stream> Streams;
+  Streams streams;
 };
 
 // "content" (as used in XEP-0166) descriptions for voice and video.
 class MediaContentDescription : public ContentDescription {
  public:
   MediaContentDescription()
-      : ssrc_(0),
-        ssrc_set_(false),
-        rtcp_mux_(false),
+      : rtcp_mux_(false),
         bandwidth_(kAutoBandwidth),
         crypto_required_(false),
-        rtp_header_extensions_set_(false) {
+        rtp_header_extensions_set_(false),
+        multistream_(false),
+        conference_mode_(false),
+        partial_(false) {
   }
 
   virtual MediaType type() const = 0;
-
-  uint32 ssrc() const { return ssrc_; }
-  bool ssrc_set() const { return ssrc_set_; }
-  void set_ssrc(uint32 ssrc) {
-    ssrc_ = ssrc;
-    ssrc_set_ = true;
-  }
+  virtual bool has_codecs() const = 0;
 
   bool rtcp_mux() const { return rtcp_mux_; }
   void set_rtcp_mux(bool mux) { rtcp_mux_ = mux; }
@@ -141,16 +161,63 @@
   bool rtp_header_extensions_set() const {
     return rtp_header_extensions_set_;
   }
+  // True iff the client supports multiple streams.
+  void set_multistream(bool multistream) { multistream_ = multistream; }
+  bool multistream() const { return multistream_;  }
+  const StreamParamsVec& streams() const {
+    return streams_;
+  }
+  // TODO: Remove this by giving mediamessage.cc access
+  // to MediaContentDescription
+  StreamParamsVec& mutable_streams() {
+    return streams_;
+  }
+  void AddStream(const StreamParams& stream) {
+    streams_.push_back(stream);
+  }
+  // Legacy streams have an ssrc, but nothing else.
+  void AddLegacyStream(uint32 ssrc) {
+    streams_.push_back(StreamParams::CreateLegacy(ssrc));
+  }
+  // Sets the CNAME of all StreamParams if it have not been set.
+  // This can be used to set the CNAME of legacy streams.
+  void SetCnameIfEmpty(const std::string& cname) {
+    for (cricket::StreamParamsVec::iterator it = streams_.begin();
+         it != streams_.end(); ++it) {
+      if (it->cname.empty())
+        it->cname = cname;
+    }
+  }
+  uint32 first_ssrc() const {
+    if (streams_.empty()) {
+      return 0;
+    }
+    return streams_[0].first_ssrc();
+  }
+  bool has_ssrcs() const {
+    if (streams_.empty()) {
+      return false;
+    }
+    return streams_[0].has_ssrcs();
+  }
+
+  void set_conference_mode(bool enable) { conference_mode_ = enable; }
+  bool conference_mode() const { return conference_mode_; }
+
+  void set_partial(bool partial) { partial_ = partial; }
+  bool partial() const { return partial_;  }
 
  protected:
-  uint32 ssrc_;
-  bool ssrc_set_;
   bool rtcp_mux_;
   int bandwidth_;
   std::vector<CryptoParams> cryptos_;
   bool crypto_required_;
   std::vector<RtpHeaderExtension> rtp_header_extensions_;
   bool rtp_header_extensions_set_;
+  bool multistream_;
+  StreamParamsVec streams_;
+  bool conference_mode_;
+  bool partial_;
 };
 
 template <class C>
@@ -161,6 +228,8 @@
   };
 
   const std::vector<C>& codecs() const { return codecs_; }
+  void set_codecs(const std::vector<C>& codecs) { codecs_ = codecs; }
+  virtual bool has_codecs() const { return !codecs_.empty(); }
   void AddCodec(const C& codec) {
     codecs_.push_back(codec);
   }
@@ -175,21 +244,22 @@
 class AudioContentDescription : public MediaContentDescriptionImpl<AudioCodec> {
  public:
   AudioContentDescription() :
-      conference_mode_(false) {}
+      agc_minus_10db_(false) {}
 
   virtual MediaType type() const { return MEDIA_TYPE_AUDIO; }
 
-  bool conference_mode() const { return conference_mode_; }
-  void set_conference_mode(bool enable) {
-    conference_mode_ = enable;
-  }
-
   const std::string &lang() const { return lang_; }
   void set_lang(const std::string &lang) { lang_ = lang; }
 
+  bool agc_minus_10db() const { return agc_minus_10db_; }
+  void set_agc_minus_10db(bool enable) {
+    agc_minus_10db_ = enable;
+  }
 
  private:
-  bool conference_mode_;
+  bool agc_minus_10db_;
+
+ private:
   std::string lang_;
 };
 
@@ -215,23 +285,40 @@
   void set_video_codecs(const VideoCodecs& codecs) { video_codecs_ = codecs; }
   SecureMediaPolicy secure() const { return secure_; }
   void set_secure(SecureMediaPolicy s) { secure_ = s; }
+  // Decides if a StreamParams shall be added to the audio and video media
+  // content in SessionDescription when CreateOffer and CreateAnswer is called
+  // even if |options| don't include a Stream. This is needed to support legacy
+  // applications. |add_legacy_| is true per default.
+  void set_add_legacy_streams(bool add_legacy) { add_legacy_ = add_legacy; }
 
-  SessionDescription* CreateOffer(const MediaSessionOptions& options);
-  SessionDescription* CreateAnswer(const SessionDescription* offer,
-                                   const MediaSessionOptions& options);
+  SessionDescription* CreateOffer(
+      const MediaSessionOptions& options,
+      const SessionDescription* current_description);
+
+  SessionDescription* CreateAnswer(
+        const SessionDescription* offer,
+        const MediaSessionOptions& options,
+        const SessionDescription* current_description);
 
  private:
   AudioCodecs audio_codecs_;
   VideoCodecs video_codecs_;
   SecureMediaPolicy secure_;
+  bool add_legacy_;
   std::string lang_;
 };
 
 // Convenience functions.
 bool IsAudioContent(const ContentInfo* content);
 bool IsVideoContent(const ContentInfo* content);
+const ContentInfo* GetFirstAudioContent(const ContentInfos& contents);
+const ContentInfo* GetFirstVideoContent(const ContentInfos& contents);
 const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc);
 const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc);
+const AudioContentDescription* GetFirstAudioContentDescription(
+    const SessionDescription* sdesc);
+const VideoContentDescription* GetFirstVideoContentDescription(
+    const SessionDescription* sdesc);
 
 }  // namespace cricket
 
diff --git a/talk/session/phone/mediasession_unittest.cc b/talk/session/phone/mediasession_unittest.cc
new file mode 100644
index 0000000..974531e
--- /dev/null
+++ b/talk/session/phone/mediasession_unittest.cc
@@ -0,0 +1,624 @@
+/*
+ * 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 <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/srtpfilter.h"
+#include "talk/session/phone/testutils.h"
+
+#ifdef HAVE_SRTP
+#define ASSERT_CRYPTO(cd, r, s, cs) \
+    ASSERT_EQ(r, cd->crypto_required()); \
+    ASSERT_EQ(s, cd->cryptos().size()); \
+    ASSERT_EQ(std::string(cs), cd->cryptos()[0].cipher_suite)
+#else
+#define ASSERT_CRYPTO(c, r, s, cs) \
+  ASSERT_EQ(false, cd->crypto_required()); \
+  ASSERT_EQ(0U, cd->cryptos().size());
+#endif
+
+using cricket::MediaSessionDescriptionFactory;
+using cricket::MediaSessionOptions;
+using cricket::MediaType;
+using cricket::SessionDescription;
+using cricket::SsrcGroup;
+using cricket::StreamParams;
+using cricket::StreamParamsVec;
+using cricket::ContentInfo;
+using cricket::CryptoParamsVec;
+using cricket::AudioContentDescription;
+using cricket::VideoContentDescription;
+using cricket::GetFirstAudioContentDescription;
+using cricket::GetFirstVideoContentDescription;
+using cricket::kAutoBandwidth;
+using cricket::AudioCodec;
+using cricket::VideoCodec;
+using cricket::NS_JINGLE_RTP;
+using cricket::MEDIA_TYPE_AUDIO;
+using cricket::MEDIA_TYPE_VIDEO;
+using cricket::SEC_ENABLED;
+using cricket::CS_AES_CM_128_HMAC_SHA1_32;
+using cricket::CS_AES_CM_128_HMAC_SHA1_80;
+
+static const AudioCodec kAudioCodecs1[] = {
+  AudioCodec(103, "ISAC",   16000, -1,    1, 5),
+  AudioCodec(102, "iLBC",   8000,  13300, 1, 4),
+  AudioCodec(0,   "PCMU",   8000,  64000, 1, 3),
+  AudioCodec(8,   "PCMA",   8000,  64000, 1, 2),
+  AudioCodec(117, "red",    8000,  0,     1, 1),
+};
+
+static const AudioCodec kAudioCodecs2[] = {
+  AudioCodec(126, "speex",  16000, 22000, 1, 3),
+  AudioCodec(127, "iLBC",   8000,  13300, 1, 2),
+  AudioCodec(0,   "PCMU",   8000,  64000, 1, 1),
+};
+
+static const AudioCodec kAudioCodecsAnswer[] = {
+  AudioCodec(102, "iLBC",   8000,  13300, 1, 2),
+  AudioCodec(0,   "PCMU",   8000,  64000, 1, 1),
+};
+
+static const VideoCodec kVideoCodecs1[] = {
+  VideoCodec(96, "H264-SVC", 320, 200, 30, 2),
+  VideoCodec(97, "H264", 320, 200, 30, 1)
+};
+
+static const VideoCodec kVideoCodecs2[] = {
+  VideoCodec(126, "H264", 320, 200, 30, 2),
+  VideoCodec(127, "H263", 320, 200, 30, 1)
+};
+
+static const VideoCodec kVideoCodecsAnswer[] = {
+  VideoCodec(97, "H264", 320, 200, 30, 2)
+};
+
+static const uint32 kSimulcastParamsSsrc[] = {10, 11, 20, 21, 30, 31};
+static const uint32 kSimSsrc[] = {10, 20, 30};
+static const uint32 kFec1Ssrc[] = {10, 11};
+static const uint32 kFec2Ssrc[] = {20, 21};
+static const uint32 kFec3Ssrc[] = {30, 31};
+
+static const char kMediaStream1[] = "stream_1";
+static const char kMediaStream2[] = "stream_2";
+static const char kVideoTrack1[] = "video_1";
+static const char kVideoTrack2[] = "video_2";
+static const char kAudioTrack1[] = "audio_1";
+static const char kAudioTrack2[] = "audio_2";
+static const char kAudioTrack3[] = "audio_3";
+
+class MediaSessionDescriptionFactoryTest : public testing::Test {
+ public:
+  MediaSessionDescriptionFactoryTest() {
+    f1_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs1));
+    f1_.set_video_codecs(MAKE_VECTOR(kVideoCodecs1));
+    f2_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs2));
+    f2_.set_video_codecs(MAKE_VECTOR(kVideoCodecs2));
+  }
+
+  // Create a video StreamParamsVec object with:
+  // - one video stream with 3 simulcast streams and FEC,
+  StreamParamsVec CreateComplexVideoStreamParamsVec() {
+    SsrcGroup sim_group("SIM", MAKE_VECTOR(kSimSsrc));
+    SsrcGroup fec_group1("FEC", MAKE_VECTOR(kFec1Ssrc));
+    SsrcGroup fec_group2("FEC", MAKE_VECTOR(kFec2Ssrc));
+    SsrcGroup fec_group3("FEC", MAKE_VECTOR(kFec3Ssrc));
+
+    std::vector<SsrcGroup> ssrc_groups;
+    ssrc_groups.push_back(sim_group);
+    ssrc_groups.push_back(fec_group1);
+    ssrc_groups.push_back(fec_group2);
+    ssrc_groups.push_back(fec_group3);
+
+    StreamParams simulcast_params;
+    simulcast_params.name = kVideoTrack1;
+    simulcast_params.ssrcs = MAKE_VECTOR(kSimulcastParamsSsrc);
+    simulcast_params.ssrc_groups = ssrc_groups;
+    simulcast_params.cname = "Video_SIM_FEC";
+    simulcast_params.sync_label = kMediaStream1;
+
+    StreamParamsVec video_streams;
+    video_streams.push_back(simulcast_params);
+
+    return video_streams;
+  }
+  bool CompareCryptoParams(const CryptoParamsVec& c1,
+                           const CryptoParamsVec& c2) {
+    if (c1.size() != c2.size())
+      return false;
+    for (size_t i = 0; i < c1.size(); ++i)
+      if (c1[i].tag != c2[i].tag || c1[i].cipher_suite != c2[i].cipher_suite ||
+          c1[i].key_params != c2[i].key_params ||
+          c1[i].session_params != c2[i].session_params)
+        return false;
+    return true;
+  }
+
+ protected:
+  MediaSessionDescriptionFactory f1_;
+  MediaSessionDescriptionFactory f2_;
+};
+
+// Create a typical audio offer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOffer) {
+  f1_.set_secure(SEC_ENABLED);
+  talk_base::scoped_ptr<SessionDescription> offer(
+      f1_.CreateOffer(MediaSessionOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  const ContentInfo* ac = offer->GetContentByName("audio");
+  const ContentInfo* vc = offer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc == NULL);
+  EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+  EXPECT_EQ(f1_.audio_codecs(), acd->codecs());
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
+  EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
+  ASSERT_CRYPTO(acd, false, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Create a typical video offer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  f1_.set_secure(SEC_ENABLED);
+  talk_base::scoped_ptr<SessionDescription>
+      offer(f1_.CreateOffer(opts, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  const ContentInfo* ac = offer->GetContentByName("audio");
+  const ContentInfo* vc = offer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
+  EXPECT_EQ(std::string(NS_JINGLE_RTP), vc->type);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+  EXPECT_EQ(f1_.audio_codecs(), acd->codecs());
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
+  EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
+  ASSERT_CRYPTO(acd, false, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+  EXPECT_EQ(f1_.video_codecs(), vcd->codecs());
+  EXPECT_NE(0U, vcd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(kAutoBandwidth, vcd->bandwidth());  // default bandwidth (auto)
+  EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on
+  ASSERT_CRYPTO(vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// Create an audio, video offer without legacy StreamParams.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       TestCreateOfferWithoutLegacyStreams) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  f1_.set_add_legacy_streams(false);
+  talk_base::scoped_ptr<SessionDescription>
+      offer(f1_.CreateOffer(opts, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  const ContentInfo* ac = offer->GetContentByName("audio");
+  const ContentInfo* vc = offer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+
+  EXPECT_FALSE(vcd->has_ssrcs());             // No StreamParams.
+  EXPECT_FALSE(acd->has_ssrcs());             // No StreamParams.
+}
+
+// Create a typical audio answer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswer) {
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  talk_base::scoped_ptr<SessionDescription> offer(
+      f1_.CreateOffer(MediaSessionOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
+  const ContentInfo* ac = answer->GetContentByName("audio");
+  const ContentInfo* vc = answer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc == NULL);
+  EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+  EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
+  EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
+  ASSERT_CRYPTO(acd, false, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Create a typical video answer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+  const ContentInfo* ac = answer->GetContentByName("audio");
+  const ContentInfo* vc = answer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
+  EXPECT_EQ(std::string(NS_JINGLE_RTP), vc->type);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+  EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
+  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
+  ASSERT_CRYPTO(acd, false, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+  EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs());
+  EXPECT_NE(0U, vcd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_TRUE(vcd->rtcp_mux());                 // negotiated rtcp-mux
+  ASSERT_CRYPTO(vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// Create an audio, video answer without legacy StreamParams.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       TestCreateAnswerWithoutLegacyStreams) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  f1_.set_add_legacy_streams(false);
+  f2_.set_add_legacy_streams(false);
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+  const ContentInfo* ac = answer->GetContentByName("audio");
+  const ContentInfo* vc = answer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+
+  EXPECT_FALSE(acd->has_ssrcs());  // No StreamParams.
+  EXPECT_FALSE(vcd->has_ssrcs());  // No StreamParams.
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestPartial) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  f1_.set_secure(SEC_ENABLED);
+  talk_base::scoped_ptr<SessionDescription>
+      offer(f1_.CreateOffer(opts, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  const ContentInfo* ac = offer->GetContentByName("audio");
+  const ContentInfo* vc = offer->GetContentByName("video");
+  AudioContentDescription* acd = const_cast<AudioContentDescription*>(
+      static_cast<const AudioContentDescription*>(ac->description));
+  VideoContentDescription* vcd = const_cast<VideoContentDescription*>(
+      static_cast<const VideoContentDescription*>(vc->description));
+
+  EXPECT_FALSE(acd->partial());  // default is false.
+  acd->set_partial(true);
+  EXPECT_TRUE(acd->partial());
+  acd->set_partial(false);
+  EXPECT_FALSE(acd->partial());
+
+  EXPECT_FALSE(vcd->partial());  // default is false.
+  vcd->set_partial(true);
+  EXPECT_TRUE(vcd->partial());
+  vcd->set_partial(false);
+  EXPECT_FALSE(vcd->partial());
+}
+
+// Create a typical video answer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerRtcpMux) {
+  MediaSessionOptions offer_opts;
+  MediaSessionOptions answer_opts;
+  answer_opts.has_video = true;
+  offer_opts.has_video = true;
+
+  talk_base::scoped_ptr<SessionDescription> offer(NULL);
+  talk_base::scoped_ptr<SessionDescription> answer(NULL);
+
+  offer_opts.rtcp_mux_enabled = true;
+  answer_opts.rtcp_mux_enabled = true;
+
+  offer.reset(f1_.CreateOffer(offer_opts, NULL));
+  answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+
+  offer_opts.rtcp_mux_enabled = true;
+  answer_opts.rtcp_mux_enabled = false;
+
+  offer.reset(f1_.CreateOffer(offer_opts, NULL));
+  answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+
+  offer_opts.rtcp_mux_enabled = false;
+  answer_opts.rtcp_mux_enabled = true;
+
+  offer.reset(f1_.CreateOffer(offer_opts, NULL));
+  answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+
+  offer_opts.rtcp_mux_enabled = false;
+  answer_opts.rtcp_mux_enabled = false;
+
+  offer.reset(f1_.CreateOffer(offer_opts, NULL));
+  answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+}
+
+// Create an audio-only answer to a video offer.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerToVideo) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  talk_base::scoped_ptr<SessionDescription>
+      offer(f1_.CreateOffer(opts, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
+  const ContentInfo* ac = answer->GetContentByName("audio");
+  const ContentInfo* vc = answer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc == NULL);
+}
+
+// Create an audio and video offer with:
+// - one video track,
+// - two audio tracks.
+// and ensure it matches what we expect. Also updates the initial offer by
+// adding a new video track and replaces one of the audio tracks.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoOffer) {
+  MediaSessionOptions opts;
+  opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
+  opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+  opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1);
+
+  f1_.set_secure(SEC_ENABLED);
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+
+  ASSERT_TRUE(offer.get() != NULL);
+  const ContentInfo* ac = offer->GetContentByName("audio");
+  const ContentInfo* vc = offer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+  EXPECT_EQ(f1_.audio_codecs(), acd->codecs());
+
+  const StreamParamsVec& audio_streams = acd->streams();
+  ASSERT_EQ(2U, audio_streams.size());
+  EXPECT_EQ(audio_streams[0].cname , audio_streams[1].cname);
+  EXPECT_EQ(kAudioTrack1, audio_streams[0].name);
+  ASSERT_EQ(1U, audio_streams[0].ssrcs.size());
+  EXPECT_NE(0U, audio_streams[0].ssrcs[0]);
+  EXPECT_EQ(kAudioTrack2, audio_streams[1].name);
+  ASSERT_EQ(1U, audio_streams[1].ssrcs.size());
+  EXPECT_NE(0U, audio_streams[1].ssrcs[0]);
+
+  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
+  EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
+  ASSERT_CRYPTO(acd, false, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+
+  EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+  EXPECT_EQ(f1_.video_codecs(), vcd->codecs());
+  ASSERT_CRYPTO(vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+
+  const StreamParamsVec& video_streams = vcd->streams();
+  ASSERT_EQ(1U, video_streams.size());
+  EXPECT_EQ(video_streams[0].cname, audio_streams[0].cname);
+  EXPECT_EQ(kVideoTrack1, video_streams[0].name);
+  EXPECT_EQ(kAutoBandwidth, vcd->bandwidth());  // default bandwidth (auto)
+  EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on
+
+
+  // Update the offer. Add a new video track that is not synched to the
+  // other tracks and replace audio track 2 with audio track 3.
+  opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
+  opts.RemoveStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
+  opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack3, kMediaStream1);
+  talk_base::scoped_ptr<SessionDescription>
+      updated_offer(f1_.CreateOffer(opts, offer.get()));
+
+  ASSERT_TRUE(updated_offer.get() != NULL);
+  ac = updated_offer->GetContentByName("audio");
+  vc = updated_offer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  const AudioContentDescription* updated_acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* updated_vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+
+  EXPECT_EQ(acd->type(), updated_acd->type());
+  EXPECT_EQ(acd->codecs(), updated_acd->codecs());
+  EXPECT_EQ(vcd->type(), updated_vcd->type());
+  EXPECT_EQ(vcd->codecs(), updated_vcd->codecs());
+  ASSERT_CRYPTO(updated_acd, false, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_TRUE(CompareCryptoParams(acd->cryptos(), updated_acd->cryptos()));
+  ASSERT_CRYPTO(updated_vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_TRUE(CompareCryptoParams(vcd->cryptos(), updated_vcd->cryptos()));
+
+  const StreamParamsVec& updated_audio_streams = updated_acd->streams();
+  ASSERT_EQ(2U, updated_audio_streams.size());
+  EXPECT_EQ(audio_streams[0], updated_audio_streams[0]);
+  EXPECT_EQ(kAudioTrack3, updated_audio_streams[1].name);  // New audio track.
+  ASSERT_EQ(1U, updated_audio_streams[1].ssrcs.size());
+  EXPECT_NE(0U, updated_audio_streams[1].ssrcs[0]);
+  EXPECT_EQ(updated_audio_streams[0].cname, updated_audio_streams[1].cname);
+
+  const StreamParamsVec& updated_video_streams = updated_vcd->streams();
+  ASSERT_EQ(2U, updated_video_streams.size());
+  EXPECT_EQ(video_streams[0], updated_video_streams[0]);
+  EXPECT_EQ(kVideoTrack2, updated_video_streams[1].name);
+  EXPECT_NE(updated_video_streams[1].cname, updated_video_streams[0].cname);
+}
+
+// Create an audio and video answer to a standard video offer with:
+// - one video track,
+// - two audio tracks.
+// and ensure it matches what we expect. Also updates the initial answer by
+// adding a new video track and removes one of the audio tracks.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoAnswer) {
+  MediaSessionOptions offer_opts;
+  offer_opts.has_video = true;
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(offer_opts,
+                                                                  NULL));
+
+  MediaSessionOptions opts;
+  opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
+  opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+  opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1);
+
+  talk_base::scoped_ptr<SessionDescription>
+      answer(f2_.CreateAnswer(offer.get(), opts, NULL));
+
+  ASSERT_TRUE(answer.get() != NULL);
+  const ContentInfo* ac = answer->GetContentByName("audio");
+  const ContentInfo* vc = answer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+  EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
+  ASSERT_CRYPTO(acd, false, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+  ASSERT_CRYPTO(vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+
+  const StreamParamsVec& audio_streams = acd->streams();
+  ASSERT_EQ(2U, audio_streams.size());
+  EXPECT_TRUE(audio_streams[0].cname ==  audio_streams[1].cname);
+  EXPECT_EQ(kAudioTrack1, audio_streams[0].name);
+  ASSERT_EQ(1U, audio_streams[0].ssrcs.size());
+  EXPECT_NE(0U, audio_streams[0].ssrcs[0]);
+  EXPECT_EQ(kAudioTrack2, audio_streams[1].name);
+  ASSERT_EQ(1U, audio_streams[1].ssrcs.size());
+  EXPECT_NE(0U, audio_streams[1].ssrcs[0]);
+
+  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
+  EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
+
+  EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+  EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs());
+
+  const StreamParamsVec& video_streams = vcd->streams();
+  ASSERT_EQ(1U, video_streams.size());
+  EXPECT_EQ(video_streams[0].cname, audio_streams[0].cname);
+  EXPECT_EQ(kVideoTrack1, video_streams[0].name);
+  EXPECT_EQ(kAutoBandwidth, vcd->bandwidth());  // default bandwidth (auto)
+  EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on
+
+  // Update the answer. Add a new video track that is not synched to the
+  // other traacks and remove 1 audio track.
+  opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
+  opts.RemoveStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
+  talk_base::scoped_ptr<SessionDescription>
+      updated_answer(f2_.CreateAnswer(offer.get(), opts, answer.get()));
+
+  ASSERT_TRUE(updated_answer.get() != NULL);
+  ac = updated_answer->GetContentByName("audio");
+  vc = updated_answer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  const AudioContentDescription* updated_acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* updated_vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+
+  ASSERT_CRYPTO(updated_acd, false, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_TRUE(CompareCryptoParams(acd->cryptos(), updated_acd->cryptos()));
+  ASSERT_CRYPTO(updated_vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_TRUE(CompareCryptoParams(vcd->cryptos(), updated_vcd->cryptos()));
+
+  EXPECT_EQ(acd->type(), updated_acd->type());
+  EXPECT_EQ(acd->codecs(), updated_acd->codecs());
+  EXPECT_EQ(vcd->type(), updated_vcd->type());
+  EXPECT_EQ(vcd->codecs(), updated_vcd->codecs());
+
+  const StreamParamsVec& updated_audio_streams = updated_acd->streams();
+  ASSERT_EQ(1U, updated_audio_streams.size());
+  EXPECT_TRUE(audio_streams[0] ==  updated_audio_streams[0]);
+
+  const StreamParamsVec& updated_video_streams = updated_vcd->streams();
+  ASSERT_EQ(2U, updated_video_streams.size());
+  EXPECT_EQ(video_streams[0], updated_video_streams[0]);
+  EXPECT_EQ(kVideoTrack2, updated_video_streams[1].name);
+  EXPECT_NE(updated_video_streams[1].cname, updated_video_streams[0].cname);
+}
diff --git a/talk/session/phone/mediasessionclient.cc b/talk/session/phone/mediasessionclient.cc
index 3491d26..07ccf13 100644
--- a/talk/session/phone/mediasessionclient.cc
+++ b/talk/session/phone/mediasessionclient.cc
@@ -36,6 +36,7 @@
 #include "talk/p2p/base/constants.h"
 #include "talk/p2p/base/parsing.h"
 #include "talk/session/phone/cryptoparams.h"
+#include "talk/session/phone/mediamessages.h"
 #include "talk/session/phone/srtpfilter.h"
 #include "talk/xmpp/constants.h"
 #include "talk/xmllite/qname.h"
@@ -198,6 +199,8 @@
   return session;
 }
 
+// TODO: Move all of the parsing and writing functions into
+// mediamessages.cc, with unit tests.
 bool ParseGingleAudioCodec(const buzz::XmlElement* element, AudioCodec* out) {
   int id = GetXmlAttr(element, QN_ID, -1);
   if (id < 0)
@@ -225,26 +228,30 @@
   return true;
 }
 
-uint32 parse_ssrc(const std::string& ssrc) {
-  // TODO: Return an error rather than defaulting to 0.
-  uint32 default_ssrc = 0U;
-  return talk_base::FromString(default_ssrc, ssrc);
+// Parses an ssrc string as a legacy stream.  If it fails, returns
+// false and fills an error message.
+bool ParseSsrcAsLegacyStream(const std::string& ssrc_str,
+                             std::vector<StreamParams>* streams,
+                             ParseError* error) {
+  if (!ssrc_str.empty()) {
+    uint32 ssrc;
+    if (!talk_base::FromString(ssrc_str, &ssrc)) {
+      return BadParse("Missing or invalid ssrc.", error);
+    }
+
+    streams->push_back(StreamParams::CreateLegacy(ssrc));
+  }
+  return true;
 }
 
 void ParseGingleSsrc(const buzz::XmlElement* parent_elem,
                      const buzz::QName& name,
-                     MediaContentDescription* content) {
+                     MediaContentDescription* media) {
   const buzz::XmlElement* ssrc_elem = parent_elem->FirstNamed(name);
   if (ssrc_elem) {
-    content->set_ssrc(parse_ssrc(ssrc_elem->BodyText()));
-  }
-}
-
-void ParseJingleSsrc(const buzz::XmlElement* desc_elem,
-                     MediaContentDescription* content) {
-  const std::string ssrc = desc_elem->Attr(QN_JINGLE_SSRC);
-  if (!ssrc.empty()) {
-    content->set_ssrc(parse_ssrc(ssrc));
+    ParseError error;
+    ParseSsrcAsLegacyStream(
+        ssrc_elem->BodyText(), &(media->mutable_streams()), &error);
   }
 }
 
@@ -304,7 +311,7 @@
 void ParseBandwidth(const buzz::XmlElement* parent_elem,
                     MediaContentDescription* media) {
   const buzz::XmlElement* bw_elem = GetXmlChild(parent_elem, LN_BANDWIDTH);
-  int bandwidth_kbps;
+  int bandwidth_kbps = -1;
   if (bw_elem && talk_base::FromString(bw_elem->BodyText(), &bandwidth_kbps)) {
     if (bandwidth_kbps >= 0) {
       media->set_bandwidth(bandwidth_kbps * 1000);
@@ -454,6 +461,23 @@
   return true;
 }
 
+bool ParseJingleStreamsOrLegacySsrc(const buzz::XmlElement* desc_elem,
+                                    MediaContentDescription* media,
+                                    ParseError* error) {
+  if (HasJingleStreams(desc_elem)) {
+    if (!ParseJingleStreams(desc_elem, &(media->mutable_streams()), error)) {
+      return false;
+    }
+  } else {
+    const std::string ssrc_str = desc_elem->Attr(QN_SSRC);
+    if (!ParseSsrcAsLegacyStream(
+            ssrc_str, &(media->mutable_streams()), error)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 bool ParseJingleAudioContent(const buzz::XmlElement* content_elem,
                              const ContentDescription** content,
                              ParseError* error) {
@@ -469,12 +493,16 @@
     }
   }
 
-  ParseJingleSsrc(content_elem, audio);
+  if (!ParseJingleStreamsOrLegacySsrc(content_elem, audio, error)) {
+    return false;
+  }
 
   if (!ParseJingleEncryption(content_elem, audio, error)) {
     return false;
   }
-  // TODO: Figure out how to integrate SSRC into Jingle.
+
+  audio->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
+
   *content = audio;
   return true;
 }
@@ -494,13 +522,17 @@
     }
   }
 
-  ParseJingleSsrc(content_elem, video);
+  if (!ParseJingleStreamsOrLegacySsrc(content_elem, video, error)) {
+    return false;
+  }
   ParseBandwidth(content_elem, video);
 
   if (!ParseJingleEncryption(content_elem, video, error)) {
     return false;
   }
-  // TODO: Figure out how to integrate SSRC into Jingle.
+
+  video->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
+
   *content = video;
   return true;
 }
@@ -625,9 +657,9 @@
        codec != audio->codecs().end(); ++codec) {
     elem->AddElement(CreateGingleAudioCodecElem(*codec));
   }
-  if (audio->ssrc_set()) {
+  if (audio->has_ssrcs()) {
     elem->AddElement(CreateGingleSsrcElem(
-        QN_GINGLE_AUDIO_SRCID, audio->ssrc()));
+        QN_GINGLE_AUDIO_SRCID, audio->first_ssrc()));
   }
 
   const CryptoParamsVec& cryptos = audio->cryptos();
@@ -636,8 +668,6 @@
                                                 QN_GINGLE_AUDIO_CRYPTO_USAGE,
                                                 crypto_required));
   }
-
-
   return elem;
 }
 
@@ -651,9 +681,9 @@
        codec != video->codecs().end(); ++codec) {
     elem->AddElement(CreateGingleVideoCodecElem(*codec));
   }
-  if (video->ssrc_set()) {
+  if (video->has_ssrcs()) {
     elem->AddElement(CreateGingleSsrcElem(
-        QN_GINGLE_VIDEO_SRCID, video->ssrc()));
+        QN_GINGLE_VIDEO_SRCID, video->first_ssrc()));
   }
   if (video->bandwidth() != kAutoBandwidth) {
     elem->AddElement(CreateBandwidthElem(QN_GINGLE_VIDEO_BANDWIDTH,
@@ -714,15 +744,19 @@
   return elem;
 }
 
-void WriteJingleSsrc(const MediaContentDescription* media,
-                     buzz::XmlElement* elem) {
-  // TODO: Right now, we have an ssrc=0 to mean "let the
-  // server choose".  In Jingle, it would make the most sense to just
-  // leave off the attribute.  But then we can't have an ssrc of 0.
-  // Once we have initiator-choosen ssrcs, we can remove this check
-  // and write out ssrc=0.
-  if (media->ssrc_set() && (media->ssrc() != 0)) {
-    AddXmlAttr(elem, QN_JINGLE_SSRC, media->ssrc());
+void WriteLegacyJingleSsrc(const MediaContentDescription* media,
+                           buzz::XmlElement* elem) {
+  if (media->has_ssrcs()) {
+    AddXmlAttr(elem, QN_SSRC, media->first_ssrc());
+  }
+}
+
+void WriteJingleStreamsOrLegacySsrc(const MediaContentDescription* media,
+                                    buzz::XmlElement* desc_elem) {
+  if (!media->multistream()) {
+    WriteLegacyJingleSsrc(media, desc_elem);
+  } else {
+    WriteJingleStreams(media->streams(), desc_elem);
   }
 }
 
@@ -732,7 +766,7 @@
       new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
 
   elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_AUDIO);
-  WriteJingleSsrc(audio, elem);
+  WriteJingleStreamsOrLegacySsrc(audio, elem);
 
   for (AudioCodecs::const_iterator codec = audio->codecs().begin();
        codec != audio->codecs().end(); ++codec) {
@@ -757,7 +791,7 @@
       new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
 
   elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_VIDEO);
-  WriteJingleSsrc(video, elem);
+  WriteJingleStreamsOrLegacySsrc(video, elem);
 
   for (VideoCodecs::const_iterator codec = video->codecs().begin();
        codec != video->codecs().end(); ++codec) {
diff --git a/talk/session/phone/mediasessionclient.h b/talk/session/phone/mediasessionclient.h
index c5c455f..893e95f 100644
--- a/talk/session/phone/mediasessionclient.h
+++ b/talk/session/phone/mediasessionclient.h
@@ -98,11 +98,11 @@
   }
 
   SessionDescription* CreateOffer(const CallOptions& options) {
-    return desc_factory_.CreateOffer(options);
+    return desc_factory_.CreateOffer(options, NULL);
   }
   SessionDescription* CreateAnswer(const SessionDescription* offer,
                                    const CallOptions& options) {
-    return desc_factory_.CreateAnswer(offer, options);
+    return desc_factory_.CreateAnswer(offer, options, NULL);
   }
 
   sigslot::signal2<Call *, Call *> SignalFocus;
diff --git a/talk/session/phone/mediasessionclient_unittest.cc b/talk/session/phone/mediasessionclient_unittest.cc
new file mode 100644
index 0000000..8232d3d
--- /dev/null
+++ b/talk/session/phone/mediasessionclient_unittest.cc
@@ -0,0 +1,2775 @@
+/*
+ * 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 <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/phone/fakedevicemanager.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlbuilder.h"
+#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmpp/constants.h"
+
+// The codecs that our FakeMediaEngine will support. Order is important, since
+// the tests check that our messages have codecs in the correct order.
+static const cricket::AudioCodec kAudioCodecs[] = {
+  cricket::AudioCodec(103, "ISAC",   16000, -1,    1, 18),
+  cricket::AudioCodec(104, "ISAC",   32000, -1,    1, 17),
+  cricket::AudioCodec(119, "ISACLC", 16000, 40000, 1, 16),
+  cricket::AudioCodec(99,  "speex",  16000, 22000, 1, 15),
+  cricket::AudioCodec(97,  "IPCMWB", 16000, 80000, 1, 14),
+  cricket::AudioCodec(9,   "G722",   16000, 64000, 1, 13),
+  cricket::AudioCodec(102, "iLBC",   8000,  13300, 1, 12),
+  cricket::AudioCodec(98,  "speex",  8000,  11000, 1, 11),
+  cricket::AudioCodec(3,   "GSM",    8000,  13000, 1, 10),
+  cricket::AudioCodec(100, "EG711U", 8000,  64000, 1, 9),
+  cricket::AudioCodec(101, "EG711A", 8000,  64000, 1, 8),
+  cricket::AudioCodec(0,   "PCMU",   8000,  64000, 1, 7),
+  cricket::AudioCodec(8,   "PCMA",   8000,  64000, 1, 6),
+  cricket::AudioCodec(126, "CN",     32000, 0,     1, 5),
+  cricket::AudioCodec(105, "CN",     16000, 0,     1, 4),
+  cricket::AudioCodec(13,  "CN",     8000,  0,     1, 3),
+  cricket::AudioCodec(117, "red",    8000,  0,     1, 2),
+  cricket::AudioCodec(106, "telephone-event", 8000, 0, 1, 1)
+};
+
+static const cricket::VideoCodec kVideoCodecs[] = {
+  cricket::VideoCodec(96, "H264-SVC", 320, 200, 30, 1)
+};
+
+const std::string kGingleCryptoOffer = \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
+    "  <usage/>                                                "  \
+    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+// Jingle offer does not have any <usage> element.
+const std::string kJingleCryptoOffer = \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
+    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+
+const std::string kGingleRequiredCryptoOffer = \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'> "\
+    "  <usage/>                                                "  \
+    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+const std::string kJingleRequiredCryptoOffer = \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'> "\
+    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+
+const std::string kGingleUnsupportedCryptoOffer = \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
+    "  <usage/>                                                "  \
+    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+const std::string kJingleUnsupportedCryptoOffer =                 \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
+    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+
+// With unsupported but with required="true"
+const std::string kGingleRequiredUnsupportedCryptoOffer =         \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'>" \
+    "  <usage/>                                                "  \
+    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'"      \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+const std::string kJingleRequiredUnsupportedCryptoOffer =                     \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'>" \
+    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'                 " \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>       " \
+    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'                  " \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>"        \
+    "</rtp:encryption>                                         ";
+
+const std::string kGingleInitiate(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='16000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='104' name='ISAC' clockrate='32000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='119' name='ISACLC' clockrate='16000' bitrate='40000' />  " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='99' name='speex' clockrate='16000' bitrate='22000' />    " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='97' name='IPCMWB' clockrate='16000' bitrate='80000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='9' name='G722' clockrate='16000' bitrate='64000' /> " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='102' name='iLBC' clockrate='8000' bitrate='13300' />" \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='98' name='speex' clockrate='8000' bitrate='11000' />" \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='3' name='GSM' clockrate='8000' bitrate='13000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='100' name='EG711U' clockrate='8000' bitrate='64000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='101' name='EG711A' clockrate='8000' bitrate='64000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='0' name='PCMU' clockrate='8000' bitrate='64000' />  " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='8' name='PCMA' clockrate='8000' bitrate='64000' />  " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='126' name='CN' clockrate='32000' />                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='105' name='CN' clockrate='16000' />                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='13' name='CN' clockrate='8000' />                   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='117' name='red' clockrate='8000' />                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='106' name='telephone-event' clockrate='8000' />     " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiate(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
+     "        <payload-type id='104' name='ISAC' clockrate='32000'/>    " \
+     "        <payload-type                                             " \
+     "          id='119' name='ISACLC' clockrate='16000'>               " \
+     "          <parameter name='bitrate' value='40000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='99' name='speex' clockrate='16000'>                 " \
+     "          <parameter name='bitrate' value='22000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='97' name='IPCMWB' clockrate='16000'>                " \
+     "          <parameter name='bitrate' value='80000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='9' name='G722' clockrate='16000'>                   " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='102' name='iLBC' clockrate='8000'>                  " \
+     "          <parameter name='bitrate' value='13300'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='98' name='speex' clockrate='8000'>                  " \
+     "          <parameter name='bitrate' value='11000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='3' name='GSM' clockrate='8000'>                     " \
+     "          <parameter name='bitrate' value='13000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='100' name='EG711U' clockrate='8000'>                " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='101' name='EG711A' clockrate='8000'>                " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='0' name='PCMU' clockrate='8000'>                    " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='8' name='PCMA' clockrate='8000'>                    " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='126' name='CN' clockrate='32000' />                 " \
+     "        <payload-type                                             " \
+     "          id='105' name='CN' clockrate='16000' />                 " \
+     "        <payload-type                                             " \
+     "          id='13' name='CN' clockrate='8000' />                   " \
+     "        <payload-type                                             " \
+     "          id='117' name='red' clockrate='8000' />                 " \
+     "        <payload-type                                             " \
+     "          id='106' name='telephone-event' clockrate='8000' />     " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+// Initiate string with a different order of supported codecs.
+// Should accept the supported ones, but with our desired order.
+const std::string kGingleInitiateDifferentPreference(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='104' name='ISAC' clockrate='32000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='97' name='IPCMWB' clockrate='16000' bitrate='80000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='9' name='G722' clockrate='16000' bitrate='64000' /> " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='119' name='ISACLC' clockrate='16000' bitrate='40000' />  " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='16000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='99' name='speex' clockrate='16000' bitrate='22000' />    " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='100' name='EG711U' clockrate='8000' bitrate='64000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='101' name='EG711A' clockrate='8000' bitrate='64000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='0' name='PCMU' clockrate='8000' bitrate='64000' />  " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='8' name='PCMA' clockrate='8000' bitrate='64000' />  " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='102' name='iLBC' clockrate='8000' bitrate='13300' />" \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='3' name='GSM' clockrate='8000' bitrate='13000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='98' name='speex' clockrate='8000' bitrate='11000' />" \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='126' name='CN' clockrate='32000' />                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='105' name='CN' clockrate='16000' />                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='13' name='CN' clockrate='8000' />                   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='117' name='red' clockrate='8000' />                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='106' name='telephone-event' clockrate='8000' />     " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateDifferentPreference(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type id='104' name='ISAC' clockrate='32000'/>    " \
+     "        <payload-type                                             " \
+     "          id='97' name='IPCMWB' clockrate='16000'>                " \
+     "          <parameter name='bitrate' value='80000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='9' name='G722' clockrate='16000'>                   " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='119' name='ISACLC' clockrate='16000'>               " \
+     "          <parameter name='bitrate' value='40000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
+     "        <payload-type                                             " \
+     "          id='99' name='speex' clockrate='16000'>                 " \
+     "          <parameter name='bitrate' value='22000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='100' name='EG711U' clockrate='8000'>                " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='101' name='EG711A' clockrate='8000'>                " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='0' name='PCMU' clockrate='8000'>                    " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='8' name='PCMA' clockrate='8000'>                    " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='102' name='iLBC' clockrate='8000'>                  " \
+     "          <parameter name='bitrate' value='13300'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='3' name='GSM' clockrate='8000'>                     " \
+     "          <parameter name='bitrate' value='13000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='98' name='speex' clockrate='8000'>                  " \
+     "          <parameter name='bitrate' value='11000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='126' name='CN' clockrate='32000' />                 " \
+     "        <payload-type                                             " \
+     "          id='105' name='CN' clockrate='16000' />                 " \
+     "        <payload-type                                             " \
+     "          id='13' name='CN' clockrate='8000' />                   " \
+     "        <payload-type                                             " \
+     "          id='117' name='red' clockrate='8000' />                 " \
+     "        <payload-type                                             " \
+     "          id='106' name='telephone-event' clockrate='8000' />     " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+const std::string kGingleVideoInitiate(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/video'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='16000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/video' " \
+     "        id='99' name='H264-SVC' framerate='30'                  " \
+     "        height='200' width='320'/>                              " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleVideoInitiate(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "    <content name='test video'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
+     "        <payload-type id='99' name='H264-SVC'>                    " \
+     "          <parameter name='height' value='200'/>                  " \
+     "          <parameter name='width' value='320'/>                   " \
+     "          <parameter name='framerate' value='30'/>                " \
+     "        </payload-type>                                           " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+const std::string kJingleVideoInitiateWithBandwidth(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "         sid='abcdef' initiator='me@domain.com/resource'>         " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "    <content name='test video'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
+     "        <payload-type id='99' name='H264-SVC'>                    " \
+     "          <parameter name='height' value='200'/>                  " \
+     "          <parameter name='width' value='320'/>                   " \
+     "          <parameter name='framerate' value='30'/>                " \
+     "        </payload-type>                                           " \
+     "        <bandwidth type='AS'>42</bandwidth>                       " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+const std::string kJingleVideoInitiateWithRtcpMux(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "         sid='abcdef' initiator='me@domain.com/resource'>         " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "    <content name='test video'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
+     "        <payload-type id='99' name='H264-SVC'>                    " \
+     "          <parameter name='height' value='200'/>                  " \
+     "          <parameter name='width' value='320'/>                   " \
+     "          <parameter name='framerate' value='30'/>                " \
+     "        </payload-type>                                           " \
+     "        <rtcp-mux/>                                               " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+// Initiate string with a combination of supported and unsupported codecs
+// Should accept the supported ones
+const std::string kGingleInitiateSomeUnsupported(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='16000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='97' name='ASDFDS' />                                " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='102' name='1010' />                                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='107' name='DFAS' />                                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='100' name='EG711U' />                               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='101' name='EG711A' />                               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='0' name='PCMU' />                                   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='110' name=':)' />                                   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='13' name='CN' />                                    " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateSomeUnsupported(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type                                           " \
+     "          id='103' name='ISAC' clockrate='16000' />             " \
+     "        <payload-type                                           " \
+     "          id='97' name='ASDFDS' />                              " \
+     "        <payload-type                                           " \
+     "          id='102' name='1010' />                               " \
+     "        <payload-type                                           " \
+     "          id='107' name='DFAS' />                               " \
+     "        <payload-type                                           " \
+     "          id='100' name='EG711U' />                             " \
+     "        <payload-type                                           " \
+     "          id='101' name='EG711A' />                             " \
+     "        <payload-type                                           " \
+     "          id='0' name='PCMU' />                                 " \
+     "        <payload-type                                           " \
+     "          id='110' name=':)' />                                 " \
+     "        <payload-type                                           " \
+     "          id='13' name='CN' />                                  " \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+const std::string kGingleVideoInitiateWithBandwidth(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/video'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='16000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/video' " \
+     "        id='99' name='H264-SVC' framerate='30'                  " \
+     "        height='200' width='320'/>                              " \
+     "      <bandwidth type='AS'>42</bandwidth>                       " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+// Initiate string without any supported codecs. Should send a reject.
+const std::string kGingleInitiateNoSupportedAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='123' name='Supercodec6000' />                       " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateNoSupportedAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type                                           " \
+     "          id='123' name='Supercodec6000' />                     " \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+// Initiate string without any codecs. Assumes ancient version of Cricket
+// and tries a session with ISAC and PCMU
+const std::string kGingleInitiateNoAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateNoAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+// The codecs are supported, but not at the given clockrates. Should send
+// a reject.
+const std::string kGingleInitiateWrongClockrates(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='8000'/>                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='97' name='IPCMWB' clockrate='1337'/>                " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='102' name='iLBC' clockrate='1982' />                " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateWrongClockrates(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type                                             " \
+     "          id='103' name='ISAC' clockrate='8000'/>                 " \
+     "        <payload-type                                             " \
+     "          id='97' name='IPCMWB' clockrate='1337'/>                " \
+     "       <payload-type                                              " \
+     "          id='102' name='iLBC' clockrate='1982' />                " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+// The codecs are supported, but not with the given number of channels.
+// Should send a reject.
+const std::string kGingleInitiateWrongChannels(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' channels='2'/>                     " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='97' name='IPCMWB' channels='3'/>                    " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateWrongChannels(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'>    " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type                                             " \
+     "          id='103' name='ISAC' channels='2'/>                     " \
+     "        <payload-type                                             " \
+     "          id='97' name='IPCMWB' channels='3'/>                    " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+// Initiate with a dynamic codec not using GIPS default payload id. Should
+// accept with provided payload id.
+const std::string kGingleInitiateDynamicAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='123' name='speex' clockrate='16000'/>               " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateDynamicAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type                                             " \
+     "          id='123' name='speex' clockrate='16000'/>               " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+// Initiate string with nothing but static codec id's. Should accept.
+const std::string kGingleInitiateStaticAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='3' />                                               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='0' />                                               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='8' />                                               " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateStaticAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type id='3' />                                 " \
+     "        <payload-type id='0' />                                 " \
+     "        <payload-type id='8' />                                 " \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+// Initiate with payload type-less codecs. Should reject.
+const std::string kGingleInitiateNoPayloadTypes(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "       name='ISAC' clockrate='16000'/>                          " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateNoPayloadTypes(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'>  " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type  name='ISAC' clockrate='16000'/>          " \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+// Initiate with unnamed dynamic codces. Should reject.
+const std::string kGingleInitiateDynamicWithoutNames(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "       id='100' clockrate='16000'/>                             " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateDynamicWithoutNames(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'>  " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type id='100' clockrate='16000'/>              " \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+const uint32 kAudioSsrc = 4294967295U;
+const uint32 kVideoSsrc = 87654321;
+// Note that this message does not specify a session ID. It must be populated
+// before use.
+const std::string kGingleAcceptWithSsrcs(
+     "<iq xmlns='jabber:client' from='me@mydomain.com'                " \
+     "    to='user@domain.com/resource' type='set' id='150'>          " \
+     "  <session xmlns='http://www.google.com/session' type='accept'  " \
+     "    initiator='me@domain.com/resource'>                         " \
+     "    <description xmlns='http://www.google.com/session/video'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='16000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='104' name='ISAC' clockrate='32000' />               " \
+     "      <src-id xmlns='http://www.google.com/session/phone'>      " \
+     "        4294967295</src-id>                                       " \
+     "      <src-id>87654321</src-id>                                 " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleAcceptWithSsrcs(
+     "<iq xmlns='jabber:client' from='me@mydomain.com'                " \
+     "    to='user@domain.com/resource' type='set' id='150'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-accept'     " \
+     "          initiator='me@domain.com/resource'>                   " \
+     "    <content name='audio'>                                      " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
+     "          media='audio' ssrc='4294967295'>                      " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>  " \
+     "        <payload-type id='104' name='ISAC' clockrate='32000'/>  " \
+     "      </description>                                            " \
+     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
+     "    </content>                                                  " \
+     "    <content name='video'>                                      " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
+     "          media='video' ssrc='87654321'>                        " \
+     "      </description>                                            " \
+     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+std::string JingleView(const std::string& ssrc,
+                       const std::string& width,
+                       const std::string& height,
+                       const std::string& framerate) {
+  // We have some slightly weird whitespace formatting to make the
+  // actual XML generated match the expected XML here.
+  return \
+      "<cli:iq"
+      "  to='me@mydomain.com'"
+      "  type='set'"
+      "  xmlns:cli='jabber:client'>"
+        "<jingle"
+      "    xmlns='urn:xmpp:jingle:1'"
+      "    action='session-info'"
+      "    sid=''>"
+          "<view xmlns='google:jingle'"
+      "      name='video'"
+      "      type='static'"
+      "      ssrc='" + ssrc + "'>"
+            "<params"
+      "        width='" + width + "'"
+      "        height='" + height + "'"
+      "        framerate='" + framerate + "'"
+      "        preference='0'/>"
+          "</view>"
+        "</jingle>"
+      "</cli:iq>";
+}
+
+std::string JingleStreamAdd(const std::string& content_name,
+                            const std::string& nick,
+                            const std::string& name,
+                            const std::string& ssrc) {
+  return \
+      "<iq"
+      "  xmlns='jabber:client'"
+      "  from='me@mydomain.com'"
+      "  to='user@domain.com/resource'"
+      "  type='set'"
+      "  id='150'>"
+      "  <jingle"
+      "    xmlns='urn:xmpp:jingle:1'"
+      "    action='description-info'>"
+      "    <content"
+      "      xmlns='urn:xmpp:jingle:1'"
+      "      name='" + content_name + "'>"
+      "      <description"
+      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
+      "        media='" + content_name + "'>"
+      "        <streams"
+      "          xmlns='google:jingle'>"
+      "          <stream"
+      "            nick='" + nick + "'"
+      "            name='" + name + "'>"
+      "            <ssrc>"  + ssrc + "</ssrc>"
+      "          </stream>"
+      "        </streams>"
+      "      </description>"
+      "    </content>"
+      "  </jingle>"
+      "</iq>";
+}
+
+std::string JingleStreamAddWithoutSsrc(const std::string& content_name,
+                                       const std::string& nick,
+                                       const std::string& name) {
+  return \
+      "<iq"
+      "  xmlns='jabber:client'"
+      "  from='me@mydomain.com'"
+      "  to='user@domain.com/resource'"
+      "  type='set'"
+      "  id='150'>"
+      "  <jingle"
+      "    xmlns='urn:xmpp:jingle:1'"
+      "    action='description-info'>"
+      "    <content"
+      "      xmlns='urn:xmpp:jingle:1'"
+      "      name='" + content_name + "'>"
+      "      <description"
+      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
+      "        media='" + content_name + "'>"
+      "        <streams"
+      "          xmlns='google:jingle'>"
+      "          <stream"
+      "            nick='" + nick + "'"
+      "            name='" + name + "'>"
+       "          </stream>"
+      "        </streams>"
+      "      </description>"
+      "    </content>"
+      "  </jingle>"
+      "</iq>";
+}
+
+std::string JingleStreamRemove(const std::string& content_name,
+                               const std::string& nick,
+                               const std::string& name) {
+  return \
+      "<iq"
+      "  xmlns='jabber:client'"
+      "  from='me@mydomain.com'"
+      "  to='user@domain.com/resource'"
+      "  type='set'"
+      "  id='150'>"
+      "  <jingle"
+      "    xmlns='urn:xmpp:jingle:1'"
+      "    action='description-info'>"
+      "    <content"
+      "      xmlns='urn:xmpp:jingle:1'"
+      "      name='" + content_name + "'>"
+      "      <description"
+      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
+      "        media='" + content_name + "'>"
+      "        <streams"
+      "          xmlns='google:jingle'>"
+      "          <stream"
+      "            nick='" + nick + "'"
+      "            name='" + name + "'/>"
+      "        </streams>"
+      "      </description>"
+      "    </content>"
+      "  </jingle>"
+      "</iq>";
+}
+
+buzz::XmlElement* CopyElement(const buzz::XmlElement* elem) {
+  return new buzz::XmlElement(*elem);
+}
+
+static std::string AddEncryption(std::string stanza, std::string encryption) {
+  std::string::size_type pos = stanza.find("</description>");
+  while (pos != std::string::npos) {
+      stanza = stanza.insert(pos, encryption);
+      pos = stanza.find("</description>", pos + encryption.length() + 1);
+  }
+  return stanza;
+}
+
+
+int IntFromJingleCodecParameter(const buzz::XmlElement* parameter,
+                                const std::string& expected_name) {
+  if (parameter) {
+    const std::string& actual_name =
+        parameter->Attr(cricket::QN_PAYLOADTYPE_PARAMETER_NAME);
+
+    EXPECT_EQ(expected_name, actual_name)
+        << "wrong parameter name.  Expected '"
+        << expected_name << "'. Actually '"
+        << actual_name << "'.";
+
+    return atoi(parameter->Attr(
+        cricket::QN_PAYLOADTYPE_PARAMETER_VALUE).c_str());
+  }
+  return 0;
+}
+
+// Parses and extracts payload and codec info from test XML.  Since
+// that XML will be in various contents (Gingle and Jingle), we need an
+// abstract parser with one concrete implementation per XML content.
+class MediaSessionTestParser {
+ public:
+  virtual buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) = 0;
+  virtual buzz::XmlElement* ContentFromAction(buzz::XmlElement* action) = 0;
+  virtual buzz::XmlElement* NextContent(buzz::XmlElement* content) = 0;
+  virtual buzz::XmlElement* PayloadTypeFromContent(
+      buzz::XmlElement* content) = 0;
+  virtual buzz::XmlElement* NextFromPayloadType(
+      buzz::XmlElement* payload_type) = 0;
+  virtual cricket::AudioCodec AudioCodecFromPayloadType(
+      const buzz::XmlElement* payload_type) = 0;
+  virtual cricket::VideoCodec VideoCodecFromPayloadType(
+      const buzz::XmlElement* payload_type) = 0;
+  virtual buzz::XmlElement* EncryptionFromContent(
+      buzz::XmlElement* content) = 0;
+  virtual buzz::XmlElement* NextFromEncryption(
+      buzz::XmlElement* encryption) = 0;
+  virtual const buzz::XmlElement* BandwidthFromContent(
+      buzz::XmlElement* content) = 0;
+  virtual const buzz::XmlElement* RtcpMuxFromContent(
+      buzz::XmlElement* content) = 0;
+  virtual bool ActionIsTerminate(const buzz::XmlElement* action) = 0;
+  virtual ~MediaSessionTestParser() {}
+};
+
+class JingleSessionTestParser : public MediaSessionTestParser {
+ public:
+  JingleSessionTestParser() : action_(NULL) {}
+
+  ~JingleSessionTestParser() {
+    delete action_;
+  }
+
+  buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) {
+    return stanza->FirstNamed(cricket::QN_JINGLE);
+  }
+
+  buzz::XmlElement* ContentFromAction(buzz::XmlElement* action) {
+    // We need to be able to use multiple contents, but the action
+    // gets deleted before we can call NextContent, so we need to
+    // stash away a copy.
+    action_ = CopyElement(action);
+    return action_->FirstNamed(cricket::QN_JINGLE_CONTENT);
+  }
+
+  buzz::XmlElement* NextContent(buzz::XmlElement* content) {
+    // For some reason, content->NextNamed(cricket::QN_JINGLE_CONTENT)
+    // doesn't work.
+    return action_->FirstNamed(cricket::QN_JINGLE_CONTENT)
+        ->NextNamed(cricket::QN_JINGLE_CONTENT);
+  }
+
+  buzz::XmlElement* PayloadTypeFromContent(buzz::XmlElement* content) {
+    buzz::XmlElement* content_desc =
+        content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
+    if (!content_desc)
+      return NULL;
+
+    return content_desc->FirstNamed(cricket::QN_JINGLE_RTP_PAYLOADTYPE);
+  }
+
+  buzz::XmlElement* NextFromPayloadType(buzz::XmlElement* payload_type) {
+    return payload_type->NextNamed(cricket::QN_JINGLE_RTP_PAYLOADTYPE);
+  }
+
+  cricket::AudioCodec AudioCodecFromPayloadType(
+      const buzz::XmlElement* payload_type) {
+    int id = 0;
+    if (payload_type->HasAttr(cricket::QN_ID))
+      id = atoi(payload_type->Attr(cricket::QN_ID).c_str());
+
+    std::string name = "";
+    if (payload_type->HasAttr(cricket::QN_NAME))
+      name = payload_type->Attr(cricket::QN_NAME);
+
+    int clockrate = 0;
+    if (payload_type->HasAttr(cricket::QN_CLOCKRATE))
+      clockrate = atoi(payload_type->Attr(cricket::QN_CLOCKRATE).c_str());
+
+    int bitrate = IntFromJingleCodecParameter(
+        payload_type->FirstNamed(cricket::QN_PARAMETER), "bitrate");
+
+    int channels = 1;
+    if (payload_type->HasAttr(cricket::QN_CHANNELS))
+      channels = atoi(payload_type->Attr(
+          cricket::QN_CHANNELS).c_str());
+
+    return cricket::AudioCodec(id, name, clockrate, bitrate, channels, 0);
+  }
+
+  cricket::VideoCodec VideoCodecFromPayloadType(
+      const buzz::XmlElement* payload_type) {
+    int id = 0;
+    if (payload_type->HasAttr(cricket::QN_ID))
+      id = atoi(payload_type->Attr(cricket::QN_ID).c_str());
+
+    std::string name = "";
+    if (payload_type->HasAttr(cricket::QN_NAME))
+      name = payload_type->Attr(cricket::QN_NAME);
+
+    int width = 0;
+    int height = 0;
+    int framerate = 0;
+    const buzz::XmlElement* param =
+        payload_type->FirstNamed(cricket::QN_PARAMETER);
+    if (param) {
+      width = IntFromJingleCodecParameter(param, "width");
+      param = param->NextNamed(cricket::QN_PARAMETER);
+      if (param) {
+        height = IntFromJingleCodecParameter(param, "height");
+        param = param->NextNamed(cricket::QN_PARAMETER);
+        if (param) {
+          framerate = IntFromJingleCodecParameter(param, "framerate");
+        }
+      }
+    }
+
+    return cricket::VideoCodec(id, name, width, height, framerate, 0);
+  }
+
+  bool ActionIsTerminate(const buzz::XmlElement* action) {
+    return (action->HasAttr(cricket::QN_ACTION) &&
+            action->Attr(cricket::QN_ACTION) == "session-terminate");
+  }
+
+  buzz::XmlElement* EncryptionFromContent(buzz::XmlElement* content) {
+    buzz::XmlElement* content_desc =
+        content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
+    if (!content_desc)
+      return NULL;
+
+    return content_desc->FirstNamed(cricket::QN_ENCRYPTION);
+  }
+
+  buzz::XmlElement* NextFromEncryption(buzz::XmlElement* encryption) {
+    return encryption->NextNamed(cricket::QN_ENCRYPTION);
+  }
+
+  const buzz::XmlElement* BandwidthFromContent(buzz::XmlElement* content) {
+    buzz::XmlElement* content_desc =
+        content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
+    if (!content_desc)
+      return NULL;
+
+    return content_desc->FirstNamed(cricket::QN_JINGLE_RTP_BANDWIDTH);
+  }
+
+  const buzz::XmlElement* RtcpMuxFromContent(buzz::XmlElement* content) {
+    return content->FirstNamed(cricket::QN_JINGLE_RTCP_MUX);
+  }
+
+ private:
+  buzz::XmlElement* action_;
+};
+
+class GingleSessionTestParser : public MediaSessionTestParser {
+ public:
+  GingleSessionTestParser() : found_content_count_(0) {}
+
+  buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) {
+    return stanza->FirstNamed(cricket::QN_GINGLE_SESSION);
+  }
+
+  buzz::XmlElement* ContentFromAction(buzz::XmlElement* session) {
+    buzz::XmlElement* content =
+        session->FirstNamed(cricket::QN_GINGLE_AUDIO_CONTENT);
+    if (content == NULL)
+      content = session->FirstNamed(cricket::QN_GINGLE_VIDEO_CONTENT);
+    return content;
+  }
+
+  // Assumes contents are in order of audio, and then video.
+  buzz::XmlElement* NextContent(buzz::XmlElement* content) {
+    found_content_count_++;
+    return content;
+  }
+
+  buzz::XmlElement* PayloadTypeFromContent(buzz::XmlElement* content) {
+    if (found_content_count_ > 0) {
+      return content->FirstNamed(cricket::QN_GINGLE_VIDEO_PAYLOADTYPE);
+    } else {
+      return content->FirstNamed(cricket::QN_GINGLE_AUDIO_PAYLOADTYPE);
+    }
+  }
+
+  buzz::XmlElement* NextFromPayloadType(buzz::XmlElement* payload_type) {
+    if (found_content_count_ > 0) {
+      return payload_type->NextNamed(cricket::QN_GINGLE_VIDEO_PAYLOADTYPE);
+    } else {
+      return payload_type->NextNamed(cricket::QN_GINGLE_AUDIO_PAYLOADTYPE);
+    }
+  }
+
+  cricket::AudioCodec AudioCodecFromPayloadType(
+      const buzz::XmlElement* payload_type) {
+    int id = 0;
+    if (payload_type->HasAttr(cricket::QN_ID))
+      id = atoi(payload_type->Attr(cricket::QN_ID).c_str());
+
+    std::string name;
+    if (payload_type->HasAttr(cricket::QN_NAME))
+      name = payload_type->Attr(cricket::QN_NAME);
+
+    int clockrate = 0;
+    if (payload_type->HasAttr(cricket::QN_CLOCKRATE))
+      clockrate = atoi(payload_type->Attr(cricket::QN_CLOCKRATE).c_str());
+
+    int bitrate = 0;
+    if (payload_type->HasAttr(cricket::QN_BITRATE))
+      bitrate = atoi(payload_type->Attr(cricket::QN_BITRATE).c_str());
+
+    int channels = 1;
+    if (payload_type->HasAttr(cricket::QN_CHANNELS))
+      channels = atoi(payload_type->Attr(cricket::QN_CHANNELS).c_str());
+
+    return cricket::AudioCodec(id, name, clockrate, bitrate, channels, 0);
+  }
+
+  cricket::VideoCodec VideoCodecFromPayloadType(
+      const buzz::XmlElement* payload_type) {
+    int id = 0;
+    if (payload_type->HasAttr(cricket::QN_ID))
+      id = atoi(payload_type->Attr(cricket::QN_ID).c_str());
+
+    std::string name;
+    if (payload_type->HasAttr(cricket::QN_NAME))
+      name = payload_type->Attr(cricket::QN_NAME);
+
+    int width = 0;
+    if (payload_type->HasAttr(cricket::QN_WIDTH))
+      width = atoi(payload_type->Attr(cricket::QN_WIDTH).c_str());
+
+    int height = 0;
+    if (payload_type->HasAttr(cricket::QN_HEIGHT))
+      height = atoi(payload_type->Attr(cricket::QN_HEIGHT).c_str());
+
+    int framerate = 1;
+    if (payload_type->HasAttr(cricket::QN_FRAMERATE))
+      framerate = atoi(payload_type->Attr(cricket::QN_FRAMERATE).c_str());
+
+    return cricket::VideoCodec(id, name, width, height, framerate, 0);
+  }
+
+  buzz::XmlElement* EncryptionFromContent(
+      buzz::XmlElement* content) {
+    return content->FirstNamed(cricket::QN_ENCRYPTION);
+  }
+
+  buzz::XmlElement* NextFromEncryption(buzz::XmlElement* encryption) {
+    return encryption->NextNamed(cricket::QN_ENCRYPTION);
+  }
+
+  const buzz::XmlElement* BandwidthFromContent(buzz::XmlElement* content) {
+    return content->FirstNamed(cricket::QN_GINGLE_VIDEO_BANDWIDTH);
+  }
+
+  const buzz::XmlElement* RtcpMuxFromContent(buzz::XmlElement* content) {
+    return NULL;
+  }
+
+  bool ActionIsTerminate(const buzz::XmlElement* session) {
+    return (session->HasAttr(buzz::QN_TYPE) &&
+            session->Attr(buzz::QN_TYPE) == "terminate");
+  }
+
+  int found_content_count_;
+};
+
+class MediaSessionClientTest : public sigslot::has_slots<> {
+ public:
+  explicit MediaSessionClientTest(MediaSessionTestParser* parser,
+                                  cricket::SignalingProtocol initial_protocol) {
+    nm_ = new talk_base::BasicNetworkManager();
+    pa_ = new cricket::BasicPortAllocator(nm_);
+    sm_ = new cricket::SessionManager(pa_, NULL);
+    fme_ = new cricket::FakeMediaEngine();
+
+    std::vector<cricket::AudioCodec>
+        audio_codecs(kAudioCodecs, kAudioCodecs + ARRAY_SIZE(kAudioCodecs));
+    fme_->SetAudioCodecs(audio_codecs);
+    std::vector<cricket::VideoCodec>
+        video_codecs(kVideoCodecs, kVideoCodecs + ARRAY_SIZE(kVideoCodecs));
+    fme_->SetVideoCodecs(video_codecs);
+
+    client_ = new cricket::MediaSessionClient(
+        buzz::Jid("user@domain.com/resource"), sm_,
+        fme_, new cricket::FakeDeviceManager());
+    client_->session_manager()->SignalOutgoingMessage.connect(
+        this, &MediaSessionClientTest::OnSendStanza);
+    client_->session_manager()->SignalSessionCreate.connect(
+        this, &MediaSessionClientTest::OnSessionCreate);
+    client_->SignalCallCreate.connect(
+        this, &MediaSessionClientTest::OnCallCreate);
+    client_->SignalCallDestroy.connect(
+        this, &MediaSessionClientTest::OnCallDestroy);
+
+    call_ = NULL;
+    parser_ = parser;
+    initial_protocol_ = initial_protocol;
+    expect_incoming_crypto_ = false;
+    expect_outgoing_crypto_ = false;
+    expected_video_bandwidth_ = cricket::kAutoBandwidth;
+    expected_video_rtcp_mux_ = false;
+  }
+
+  ~MediaSessionClientTest() {
+    delete client_;
+    delete sm_;
+    delete pa_;
+    delete nm_;
+    delete parser_;
+  }
+
+  buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) {
+    return parser_->ActionFromStanza(stanza);
+  }
+
+  buzz::XmlElement* ContentFromAction(buzz::XmlElement* action) {
+    return parser_->ContentFromAction(action);
+  }
+
+  buzz::XmlElement* PayloadTypeFromContent(buzz::XmlElement* payload) {
+    return parser_->PayloadTypeFromContent(payload);
+  }
+
+  buzz::XmlElement* NextFromPayloadType(buzz::XmlElement* payload_type) {
+    return parser_->NextFromPayloadType(payload_type);
+  }
+
+  buzz::XmlElement* EncryptionFromContent(buzz::XmlElement* content) {
+    return parser_->EncryptionFromContent(content);
+  }
+
+  buzz::XmlElement* NextFromEncryption(buzz::XmlElement* encryption) {
+    return parser_->NextFromEncryption(encryption);
+  }
+
+  cricket::AudioCodec AudioCodecFromPayloadType(
+      const buzz::XmlElement* payload_type) {
+    return parser_->AudioCodecFromPayloadType(payload_type);
+  }
+
+  const cricket::AudioContentDescription* GetFirstAudioContentDescription(
+      const cricket::SessionDescription* sdesc) {
+    const cricket::ContentInfo* content =
+        cricket::GetFirstAudioContent(sdesc);
+    if (content == NULL)
+      return NULL;
+    return static_cast<const cricket::AudioContentDescription*>(
+        content->description);
+  }
+
+  const cricket::VideoContentDescription* GetFirstVideoContentDescription(
+      const cricket::SessionDescription* sdesc) {
+    const cricket::ContentInfo* content =
+        cricket::GetFirstVideoContent(sdesc);
+    if (content == NULL)
+      return NULL;
+    return static_cast<const cricket::VideoContentDescription*>(
+        content->description);
+  }
+
+  void CheckCryptoFromGoodIncomingInitiate(const cricket::Session* session) {
+    ASSERT_TRUE(session != NULL);
+    const cricket::AudioContentDescription* content =
+        GetFirstAudioContentDescription(session->remote_description());
+    ASSERT_TRUE(content != NULL);
+    ASSERT_EQ(2U, content->cryptos().size());
+    ASSERT_EQ(145, content->cryptos()[0].tag);
+    ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", content->cryptos()[0].cipher_suite);
+    ASSERT_EQ("inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9",
+              content->cryptos()[0].key_params);
+    ASSERT_EQ(51, content->cryptos()[1].tag);
+    ASSERT_EQ("AES_CM_128_HMAC_SHA1_80", content->cryptos()[1].cipher_suite);
+    ASSERT_EQ("inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy",
+              content->cryptos()[1].key_params);
+  }
+
+  void CheckCryptoForGoodOutgoingAccept(const cricket::Session* session) {
+    const cricket::AudioContentDescription* content =
+        GetFirstAudioContentDescription(session->local_description());
+    ASSERT_EQ(1U, content->cryptos().size());
+    ASSERT_EQ(145, content->cryptos()[0].tag);
+    ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", content->cryptos()[0].cipher_suite);
+    ASSERT_EQ(47U, content->cryptos()[0].key_params.size());
+  }
+
+  void CheckBadCryptoFromIncomingInitiate(const cricket::Session* session) {
+    const cricket::AudioContentDescription* content =
+        GetFirstAudioContentDescription(session->remote_description());
+    ASSERT_EQ(1U, content->cryptos().size());
+    ASSERT_EQ(145, content->cryptos()[0].tag);
+    ASSERT_EQ("NOT_SUPPORTED", content->cryptos()[0].cipher_suite);
+    ASSERT_EQ("inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9",
+              content->cryptos()[0].key_params);
+  }
+
+  void CheckNoCryptoForOutgoingAccept(const cricket::Session* session) {
+    const cricket::AudioContentDescription* content =
+        GetFirstAudioContentDescription(session->local_description());
+    ASSERT_TRUE(content->cryptos().empty());
+  }
+
+  void CheckVideoBandwidth(int expected_bandwidth,
+                           const cricket::SessionDescription* sdesc) {
+    const cricket::VideoContentDescription* video =
+        GetFirstVideoContentDescription(sdesc);
+    if (video != NULL) {
+      ASSERT_EQ(expected_bandwidth, video->bandwidth());
+    }
+  }
+
+  void CheckVideoRtcpMux(bool expected_video_rtcp_mux,
+                         const cricket::SessionDescription* sdesc) {
+    const cricket::VideoContentDescription* video =
+        GetFirstVideoContentDescription(sdesc);
+    if (video != NULL) {
+      ASSERT_EQ(expected_video_rtcp_mux, video->rtcp_mux());
+    }
+  }
+
+  void CheckAudioSsrcForIncomingAccept(const cricket::Session* session) {
+    const cricket::AudioContentDescription* audio =
+        GetFirstAudioContentDescription(session->remote_description());
+    ASSERT_TRUE(audio != NULL);
+    ASSERT_EQ(kAudioSsrc, audio->first_ssrc());
+  }
+
+  void CheckVideoSsrcForIncomingAccept(const cricket::Session* session) {
+    const cricket::VideoContentDescription* video =
+        GetFirstVideoContentDescription(session->remote_description());
+    ASSERT_TRUE(video != NULL);
+    ASSERT_EQ(kVideoSsrc, video->first_ssrc());
+  }
+
+  void TestGoodIncomingInitiate(const std::string &initiate_string,
+                                buzz::XmlElement** element) {
+    *element = NULL;
+
+    buzz::XmlElement* el = buzz::XmlElement::ForStr(initiate_string);
+    client_->session_manager()->OnIncomingMessage(el);
+    ASSERT_TRUE(call_ != NULL);
+    ASSERT_TRUE(call_->sessions()[0] != NULL);
+    ASSERT_EQ(cricket::Session::STATE_RECEIVEDINITIATE,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
+    delete stanzas_[0];
+    stanzas_.clear();
+    CheckVideoBandwidth(expected_video_bandwidth_,
+                        call_->sessions()[0]->remote_description());
+    CheckVideoRtcpMux(expected_video_rtcp_mux_,
+                      call_->sessions()[0]->remote_description());
+    if (expect_incoming_crypto_) {
+      CheckCryptoFromGoodIncomingInitiate(call_->sessions()[0]);
+    }
+
+    // TODO: Add tests for sending <bandwidth> in accept.
+    cricket::CallOptions opts;
+    opts.has_video = (GetFirstVideoContentDescription(
+        call_->sessions()[0]->remote_description()) != NULL);
+    call_->AcceptSession(call_->sessions()[0], opts);
+    ASSERT_EQ(cricket::Session::STATE_SENTACCEPT,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+
+    buzz::XmlElement* e = ActionFromStanza(stanzas_[0]);
+    ASSERT_TRUE(e != NULL);
+    ASSERT_TRUE(ContentFromAction(e) != NULL);
+    *element = CopyElement(ContentFromAction(e));
+    ASSERT_TRUE(*element != NULL);
+    delete stanzas_[0];
+    stanzas_.clear();
+    if (expect_outgoing_crypto_) {
+      CheckCryptoForGoodOutgoingAccept(call_->sessions()[0]);
+    }
+
+    call_->Terminate();
+    ASSERT_EQ(cricket::Session::STATE_SENTTERMINATE,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+    e = ActionFromStanza(stanzas_[0]);
+    ASSERT_TRUE(e != NULL);
+    ASSERT_TRUE(parser_->ActionIsTerminate(e));
+    delete stanzas_[0];
+    stanzas_.clear();
+  }
+
+  void TestBadIncomingInitiate(const std::string& initiate_string) {
+    buzz::XmlElement* el = buzz::XmlElement::ForStr(initiate_string);
+    client_->session_manager()->OnIncomingMessage(el);
+    ASSERT_TRUE(call_ != NULL);
+    ASSERT_TRUE(call_->sessions()[0] != NULL);
+    ASSERT_EQ(cricket::Session::STATE_SENTREJECT,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(2U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[1]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[1]->Attr(buzz::QN_TYPE));
+    delete stanzas_[0];
+    stanzas_.clear();
+  }
+
+  void TestGoodOutgoingInitiate() {
+    cricket::CallOptions options;
+    TestGoodOutgoingInitiate(options);
+  }
+
+  void TestGoodOutgoingInitiate(const cricket::CallOptions& options) {
+    client_->CreateCall();
+    ASSERT_TRUE(call_ != NULL);
+    call_->InitiateSession(buzz::Jid("me@mydomain.com"), options);
+    ASSERT_TRUE(call_->sessions()[0] != NULL);
+    ASSERT_EQ(cricket::Session::STATE_SENTINITIATE,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+    buzz::XmlElement* action = ActionFromStanza(stanzas_[0]);
+    ASSERT_TRUE(action != NULL);
+    buzz::XmlElement* content = ContentFromAction(action);
+    ASSERT_TRUE(content != NULL);
+
+    buzz::XmlElement* e = PayloadTypeFromContent(content);
+    ASSERT_TRUE(e != NULL);
+    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(103, codec.id);
+    ASSERT_EQ("ISAC", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(0, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(104, codec.id);
+    ASSERT_EQ("ISAC", codec.name);
+    ASSERT_EQ(32000, codec.clockrate);
+    ASSERT_EQ(0, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(119, codec.id);
+    ASSERT_EQ("ISACLC", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(40000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(99, codec.id);
+    ASSERT_EQ("speex", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(22000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(97, codec.id);
+    ASSERT_EQ("IPCMWB", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(80000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+     e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(9, codec.id);
+    ASSERT_EQ("G722", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(64000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(102, codec.id);
+    ASSERT_EQ("iLBC", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(13300, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(98, codec.id);
+    ASSERT_EQ("speex", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(11000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(3, codec.id);
+    ASSERT_EQ("GSM", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(13000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(100, codec.id);
+    ASSERT_EQ("EG711U", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(64000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(101, codec.id);
+    ASSERT_EQ("EG711A", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(64000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(0, codec.id);
+    ASSERT_EQ("PCMU", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(64000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(8, codec.id);
+    ASSERT_EQ("PCMA", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(64000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(126, codec.id);
+    ASSERT_EQ("CN", codec.name);
+    ASSERT_EQ(32000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(105, codec.id);
+    ASSERT_EQ("CN", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(13, codec.id);
+    ASSERT_EQ("CN", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(117, codec.id);
+    ASSERT_EQ("red", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(106, codec.id);
+    ASSERT_EQ("telephone-event", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e == NULL);
+
+    if (expect_outgoing_crypto_) {
+      buzz::XmlElement* encryption = EncryptionFromContent(content);
+      ASSERT_TRUE(encryption != NULL);
+
+      if (client_->secure() == cricket::SEC_REQUIRED) {
+        ASSERT_TRUE(cricket::GetXmlAttr(
+            encryption, cricket::QN_ENCRYPTION_REQUIRED, false));
+      }
+
+      if (content->Name().Namespace() == cricket::NS_GINGLE_AUDIO) {
+        e = encryption->FirstNamed(cricket::QN_GINGLE_AUDIO_CRYPTO_USAGE);
+        ASSERT_TRUE(e != NULL);
+        ASSERT_TRUE(
+            e->NextNamed(cricket::QN_GINGLE_AUDIO_CRYPTO_USAGE) == NULL);
+        ASSERT_TRUE(
+            e->FirstNamed(cricket::QN_GINGLE_VIDEO_CRYPTO_USAGE) == NULL);
+      }
+
+      e = encryption->FirstNamed(cricket::QN_CRYPTO);
+      ASSERT_TRUE(e != NULL);
+      ASSERT_EQ("0", e->Attr(cricket::QN_CRYPTO_TAG));
+      ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", e->Attr(cricket::QN_CRYPTO_SUITE));
+      std::string key_0 = e->Attr(cricket::QN_CRYPTO_KEY_PARAMS);
+      ASSERT_EQ(47U, key_0.length());
+      ASSERT_EQ("inline:", key_0.substr(0, 7));
+
+      e = e->NextNamed(cricket::QN_CRYPTO);
+      ASSERT_TRUE(e != NULL);
+      ASSERT_EQ("1", e->Attr(cricket::QN_CRYPTO_TAG));
+      ASSERT_EQ("AES_CM_128_HMAC_SHA1_80", e->Attr(cricket::QN_CRYPTO_SUITE));
+      std::string key_1 = e->Attr(cricket::QN_CRYPTO_KEY_PARAMS);
+      ASSERT_EQ(47U, key_1.length());
+      ASSERT_EQ("inline:", key_1.substr(0, 7));
+      ASSERT_NE(key_0, key_1);
+
+      encryption = NextFromEncryption(encryption);
+      ASSERT_TRUE(encryption == NULL);
+    }
+
+    if (options.has_video) {
+      CheckVideoBandwidth(options.video_bandwidth,
+                          call_->sessions()[0]->local_description());
+      CheckVideoRtcpMux(expected_video_rtcp_mux_,
+                        call_->sessions()[0]->remote_description());
+      content = parser_->NextContent(content);
+      const buzz::XmlElement* bandwidth =
+          parser_->BandwidthFromContent(content);
+      if (options.video_bandwidth == cricket::kAutoBandwidth) {
+        ASSERT_TRUE(bandwidth == NULL);
+      } else {
+        ASSERT_TRUE(bandwidth != NULL);
+        ASSERT_EQ("AS", bandwidth->Attr(buzz::QName("", "type")));
+        ASSERT_EQ(talk_base::ToString(options.video_bandwidth / 1000),
+                  bandwidth->BodyText());
+      }
+    }
+
+    delete stanzas_[0];
+    stanzas_.clear();
+  }
+
+  void TestHasAllSupportedAudioCodecs(buzz::XmlElement* e) {
+    ASSERT_TRUE(e != NULL);
+
+    e = PayloadTypeFromContent(e);
+    ASSERT_TRUE(e != NULL);
+    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(103, codec.id);
+    ASSERT_EQ("ISAC", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(104, codec.id);
+    ASSERT_EQ("ISAC", codec.name);
+    ASSERT_EQ(32000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(119, codec.id);
+    ASSERT_EQ("ISACLC", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(40000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(99, codec.id);
+    ASSERT_EQ("speex", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(22000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(97, codec.id);
+    ASSERT_EQ("IPCMWB", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(80000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(9, codec.id);
+    ASSERT_EQ("G722", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(64000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(102, codec.id);
+    ASSERT_EQ("iLBC", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(13300, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(98, codec.id);
+    ASSERT_EQ("speex", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(11000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(3, codec.id);
+    ASSERT_EQ("GSM", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(13000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(100, codec.id);
+    ASSERT_EQ("EG711U", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(64000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(101, codec.id);
+    ASSERT_EQ("EG711A", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(64000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(0, codec.id);
+    ASSERT_EQ("PCMU", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(64000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(8, codec.id);
+    ASSERT_EQ("PCMA", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(64000, codec.bitrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(126, codec.id);
+    ASSERT_EQ("CN", codec.name);
+    ASSERT_EQ(32000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(105, codec.id);
+    ASSERT_EQ("CN", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(13, codec.id);
+    ASSERT_EQ("CN", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(117, codec.id);
+    ASSERT_EQ("red", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(106, codec.id);
+    ASSERT_EQ("telephone-event", codec.name);
+    ASSERT_EQ(8000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e == NULL);
+  }
+
+  void TestCodecsOfVideoInitiate(buzz::XmlElement* content) {
+    ASSERT_TRUE(content != NULL);
+    buzz::XmlElement* payload_type = PayloadTypeFromContent(content);
+    ASSERT_TRUE(payload_type != NULL);
+    cricket::AudioCodec codec = AudioCodecFromPayloadType(payload_type);
+    ASSERT_EQ(103, codec.id);
+    ASSERT_EQ("ISAC", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    content = parser_->NextContent(content);
+    ASSERT_TRUE(content != NULL);
+    payload_type = PayloadTypeFromContent(content);
+    ASSERT_TRUE(payload_type != NULL);
+    cricket::VideoCodec vcodec =
+        parser_->VideoCodecFromPayloadType(payload_type);
+    ASSERT_EQ(99, vcodec.id);
+    ASSERT_EQ("H264-SVC", vcodec.name);
+    ASSERT_EQ(320, vcodec.width);
+    ASSERT_EQ(200, vcodec.height);
+    ASSERT_EQ(30, vcodec.framerate);
+  }
+
+  void TestHasAudioCodecsFromInitiateSomeUnsupported(buzz::XmlElement* e) {
+    ASSERT_TRUE(e != NULL);
+    e = PayloadTypeFromContent(e);
+    ASSERT_TRUE(e != NULL);
+
+    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(103, codec.id);
+    ASSERT_EQ("ISAC", codec.name);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(100, codec.id);
+    ASSERT_EQ("EG711U", codec.name);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(101, codec.id);
+    ASSERT_EQ("EG711A", codec.name);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(0, codec.id);
+    ASSERT_EQ("PCMU", codec.name);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(13, codec.id);
+    ASSERT_EQ("CN", codec.name);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e == NULL);
+  }
+
+  void TestHasAudioCodecsFromInitiateDynamicAudioCodecs(
+      buzz::XmlElement* e) {
+    ASSERT_TRUE(e != NULL);
+    e = PayloadTypeFromContent(e);
+    ASSERT_TRUE(e != NULL);
+
+    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(123, codec.id);
+    ASSERT_EQ(16000, codec.clockrate);
+    ASSERT_EQ(1, codec.channels);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e == NULL);
+  }
+
+  void TestHasDefaultAudioCodecs(buzz::XmlElement* e) {
+    ASSERT_TRUE(e != NULL);
+    e = PayloadTypeFromContent(e);
+    ASSERT_TRUE(e != NULL);
+
+    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(103, codec.id);
+    ASSERT_EQ("ISAC", codec.name);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(0, codec.id);
+    ASSERT_EQ("PCMU", codec.name);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e == NULL);
+  }
+
+  void TestHasAudioCodecsFromInitiateStaticAudioCodecs(
+      buzz::XmlElement* e) {
+    ASSERT_TRUE(e != NULL);
+    e = PayloadTypeFromContent(e);
+    ASSERT_TRUE(e != NULL);
+
+    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(3, codec.id);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(0, codec.id);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e != NULL);
+    codec = AudioCodecFromPayloadType(e);
+    ASSERT_EQ(8, codec.id);
+
+    e = NextFromPayloadType(e);
+    ASSERT_TRUE(e == NULL);
+  }
+
+  void TestGingleInitiateWithUnsupportedCrypto(
+      const std::string &initiate_string,
+      buzz::XmlElement** element) {
+    *element = NULL;
+
+    buzz::XmlElement* el = buzz::XmlElement::ForStr(initiate_string);
+    client_->session_manager()->OnIncomingMessage(el);
+
+    ASSERT_EQ(cricket::Session::STATE_RECEIVEDINITIATE,
+              call_->sessions()[0]->state());
+    delete stanzas_[0];
+    stanzas_.clear();
+    CheckBadCryptoFromIncomingInitiate(call_->sessions()[0]);
+
+    call_->AcceptSession(call_->sessions()[0], cricket::CallOptions());
+    delete stanzas_[0];
+    stanzas_.clear();
+    CheckNoCryptoForOutgoingAccept(call_->sessions()[0]);
+
+    call_->Terminate();
+    ASSERT_EQ(cricket::Session::STATE_SENTTERMINATE,
+              call_->sessions()[0]->state());
+    delete stanzas_[0];
+    stanzas_.clear();
+  }
+
+  void TestIncomingAcceptWithSsrcs(const std::string& accept_string) {
+    client_->CreateCall();
+    ASSERT_TRUE(call_ != NULL);
+
+    cricket::CallOptions options;
+    options.has_video = true;
+    options.is_muc = true;
+
+    call_->InitiateSession(buzz::Jid("me@mydomain.com"), options);
+    ASSERT_TRUE(call_->sessions()[0] != NULL);
+    ASSERT_EQ(cricket::Session::STATE_SENTINITIATE,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+    buzz::XmlElement* action = ActionFromStanza(stanzas_[0]);
+    ASSERT_TRUE(action != NULL);
+    buzz::XmlElement* content = ContentFromAction(action);
+    ASSERT_TRUE(content != NULL);
+    if (initial_protocol_ == cricket::PROTOCOL_JINGLE) {
+      buzz::XmlElement* content_desc =
+          content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
+      ASSERT_TRUE(content_desc != NULL);
+      ASSERT_EQ("", content_desc->Attr(cricket::QN_SSRC));
+    }
+    delete stanzas_[0];
+    stanzas_.clear();
+
+    // We need to insert the session ID into the session accept message.
+    buzz::XmlElement* el = buzz::XmlElement::ForStr(accept_string);
+    const std::string sid = call_->sessions()[0]->id();
+    if (initial_protocol_ == cricket::PROTOCOL_JINGLE) {
+      buzz::XmlElement* jingle = el->FirstNamed(cricket::QN_JINGLE);
+      jingle->SetAttr(cricket::QN_SID, sid);
+    } else {
+      buzz::XmlElement* session = el->FirstNamed(cricket::QN_GINGLE_SESSION);
+      session->SetAttr(cricket::QN_ID, sid);
+    }
+
+    client_->session_manager()->OnIncomingMessage(el);
+
+    ASSERT_EQ(cricket::Session::STATE_RECEIVEDACCEPT,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
+    delete stanzas_[0];
+    stanzas_.clear();
+
+    CheckAudioSsrcForIncomingAccept(call_->sessions()[0]);
+    CheckVideoSsrcForIncomingAccept(call_->sessions()[0]);
+  }
+
+  size_t ClearStanzas() {
+    size_t size = stanzas_.size();
+    for (size_t i = 0; i < size; i++) {
+      delete stanzas_[i];
+    }
+    stanzas_.clear();
+    return size;
+  }
+
+  void SetJingleSid(buzz::XmlElement* stanza) {
+    buzz::XmlElement* jingle =
+        stanza->FirstNamed(cricket::QN_JINGLE);
+    jingle->SetAttr(cricket::QN_SID, call_->sessions()[0]->id());
+  }
+
+  void TestStreamsUpdateAndViewRequests() {
+    cricket::CallOptions options;
+    options.has_video = true;
+    options.is_muc = true;
+
+    client_->CreateCall();
+    call_->InitiateSession(buzz::Jid("me@mydomain.com"), options);
+    ASSERT_EQ(1U, ClearStanzas());
+    ASSERT_EQ(0U, last_streams_added_.audio().size());
+    ASSERT_EQ(0U, last_streams_added_.video().size());
+    ASSERT_EQ(0U, last_streams_removed_.audio().size());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+    talk_base::scoped_ptr<buzz::XmlElement> accept_stanza(
+        buzz::XmlElement::ForStr(kJingleAcceptWithSsrcs));
+    SetJingleSid(accept_stanza.get());
+    client_->session_manager()->OnIncomingMessage(accept_stanza.get());
+    ASSERT_EQ(cricket::Session::STATE_RECEIVEDACCEPT,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
+    ClearStanzas();
+    call_->sessions()[0]->SetState(cricket::Session::STATE_INPROGRESS);
+
+    talk_base::scoped_ptr<buzz::XmlElement> streams_stanza(
+        buzz::XmlElement::ForStr(
+            JingleStreamAdd("video", "Bob", "video1", "ABC")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    // First one is ignored because of bad syntax.
+    ASSERT_EQ(1U, stanzas_.size());
+    // TODO: Figure out how to make this an ERROR rather than RESULT.
+    ASSERT_EQ(std::string(buzz::STR_ERROR), stanzas_[0]->Attr(buzz::QN_TYPE));
+    ClearStanzas();
+    ASSERT_EQ(0U, last_streams_added_.audio().size());
+    ASSERT_EQ(0U, last_streams_added_.video().size());
+    ASSERT_EQ(0U, last_streams_removed_.audio().size());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("audio", "Bob", "audio1", "1234")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.audio().size());
+    ASSERT_EQ("Bob", last_streams_added_.audio()[0].nick);
+    ASSERT_EQ(1U, last_streams_added_.audio()[0].ssrcs.size());
+    ASSERT_EQ(1234U, last_streams_added_.audio()[0].first_ssrc());
+
+    // Ignores adds without ssrcs.
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAddWithoutSsrc("audio", "Bob", "audioX")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.audio().size());
+    ASSERT_EQ(1234U, last_streams_added_.audio()[0].first_ssrc());
+
+    // Ignores stream updates with unknown content names. (Don't terminate).
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAddWithoutSsrc("foo", "Bob", "foo")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("audio", "Joe", "audio1", "2468")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.audio().size());
+    ASSERT_EQ("Joe", last_streams_added_.audio()[0].nick);
+    ASSERT_EQ(1U, last_streams_added_.audio()[0].ssrcs.size());
+    ASSERT_EQ(2468U, last_streams_added_.audio()[0].first_ssrc());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("video", "Bob", "video1", "5678")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.video().size());
+    ASSERT_EQ("Bob", last_streams_added_.video()[0].nick);
+    ASSERT_EQ(1U, last_streams_added_.video()[0].ssrcs.size());
+    ASSERT_EQ(5678U, last_streams_added_.video()[0].first_ssrc());
+
+    // We're testing that a "duplicate" is effectively ignored.
+    last_streams_added_.mutable_video()->clear();
+    last_streams_removed_.mutable_video()->clear();
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("video", "Bob", "video1", "5678")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(0U, last_streams_added_.video().size());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("video", "Bob", "video2", "5679")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.video().size());
+    ASSERT_EQ("Bob", last_streams_added_.video()[0].nick);
+    ASSERT_EQ(1U, last_streams_added_.video()[0].ssrcs.size());
+    ASSERT_EQ(5679U, last_streams_added_.video()[0].first_ssrc());
+
+    cricket::FakeVoiceMediaChannel* voice_channel = fme_->GetVoiceChannel(0);
+    ASSERT_TRUE(voice_channel != NULL);
+    ASSERT_TRUE(voice_channel->HasRecvStream(1234U));
+    ASSERT_TRUE(voice_channel->HasRecvStream(2468U));
+    cricket::FakeVideoMediaChannel* video_channel = fme_->GetVideoChannel(0);
+    ASSERT_TRUE(video_channel != NULL);
+    ASSERT_TRUE(video_channel->HasRecvStream(5678U));
+    ClearStanzas();
+
+    cricket::ViewRequest viewRequest;
+    cricket::StaticVideoView staticVideoView(5678U, 640, 480, 30);
+    viewRequest.static_video_views.push_back(staticVideoView);
+    talk_base::scoped_ptr<buzz::XmlElement> expected_view_elem(
+        buzz::XmlElement::ForStr(JingleView("5678", "640", "480", "30")));
+    SetJingleSid(expected_view_elem.get());
+
+    ASSERT_TRUE(
+        call_->SendViewRequest(call_->sessions()[0], viewRequest));
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_EQ(expected_view_elem->Str(), stanzas_[0]->Str());
+    ClearStanzas();
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamRemove("audio", "Bob", "audio1")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_removed_.audio().size());
+    ASSERT_EQ(1U, last_streams_removed_.audio()[0].ssrcs.size());
+    EXPECT_EQ(1234U, last_streams_removed_.audio()[0].first_ssrc());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamRemove("video", "Bob", "video1")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_removed_.video().size());
+    ASSERT_EQ(1U, last_streams_removed_.video()[0].ssrcs.size());
+    EXPECT_EQ(5678U, last_streams_removed_.video()[0].first_ssrc());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamRemove("video", "Bob", "video2")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_removed_.video().size());
+    ASSERT_EQ(1U, last_streams_removed_.video()[0].ssrcs.size());
+    EXPECT_EQ(5679U, last_streams_removed_.video()[0].first_ssrc());
+
+    // Duplicate removal: should be ignored.
+    last_streams_removed_.mutable_audio()->clear();
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamRemove("audio", "Bob", "audio1")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(0U, last_streams_removed_.audio().size());
+
+    // Duplicate removal: should be ignored.
+    last_streams_removed_.mutable_video()->clear();
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamRemove("video", "Bob", "video1")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+    voice_channel = fme_->GetVoiceChannel(0);
+    ASSERT_TRUE(voice_channel != NULL);
+    ASSERT_FALSE(voice_channel->HasRecvStream(1234U));
+    ASSERT_TRUE(voice_channel->HasRecvStream(2468U));
+    video_channel = fme_->GetVideoChannel(0);
+    ASSERT_TRUE(video_channel != NULL);
+    ASSERT_FALSE(video_channel->HasRecvStream(5678U));
+
+    // Fails because ssrc is now invalid.
+    ASSERT_FALSE(
+        call_->SendViewRequest(call_->sessions()[0], viewRequest));
+
+    ClearStanzas();
+  }
+
+  void MakeSignalingSecure(cricket::SecureMediaPolicy secure) {
+    client_->set_secure(secure);
+  }
+
+  void ExpectCrypto(cricket::SecureMediaPolicy secure) {
+    MakeSignalingSecure(secure);
+    expect_incoming_crypto_ = true;
+#ifdef HAVE_SRTP
+    expect_outgoing_crypto_ = true;
+#endif
+  }
+
+  void ExpectVideoBandwidth(int bandwidth) {
+    expected_video_bandwidth_ = bandwidth;
+  }
+
+  void ExpectVideoRtcpMux(bool rtcp_mux) {
+    expected_video_rtcp_mux_ = rtcp_mux;
+  }
+
+ private:
+  void OnSendStanza(cricket::SessionManager* manager,
+                    const buzz::XmlElement* stanza) {
+    LOG(LS_INFO) << stanza->Str();
+    stanzas_.push_back(new buzz::XmlElement(*stanza));
+  }
+
+  void OnSessionCreate(cricket::Session* session, bool initiate) {
+    session->set_current_protocol(initial_protocol_);
+  }
+
+  void OnCallCreate(cricket::Call *call) {
+    call_ = call;
+    call->SignalMediaStreamsUpdate.connect(
+        this, &MediaSessionClientTest::OnMediaStreamsUpdate);
+  }
+
+  void OnCallDestroy(cricket::Call *call) {
+    call_ = NULL;
+  }
+
+  void OnMediaStreamsUpdate(cricket::Call *call,
+                            cricket::Session *session,
+                            const cricket::MediaStreams& added,
+                            const cricket::MediaStreams& removed) {
+    last_streams_added_.CopyFrom(added);
+    last_streams_removed_.CopyFrom(removed);
+  }
+
+  talk_base::NetworkManager* nm_;
+  cricket::PortAllocator* pa_;
+  cricket::SessionManager* sm_;
+  cricket::FakeMediaEngine* fme_;
+  cricket::MediaSessionClient* client_;
+
+  cricket::Call* call_;
+  std::vector<buzz::XmlElement* > stanzas_;
+  MediaSessionTestParser* parser_;
+  cricket::SignalingProtocol initial_protocol_;
+  bool expect_incoming_crypto_;
+  bool expect_outgoing_crypto_;
+  int expected_video_bandwidth_;
+  bool expected_video_rtcp_mux_;
+  cricket::MediaStreams last_streams_added_;
+  cricket::MediaStreams last_streams_removed_;
+};
+
+MediaSessionClientTest* GingleTest() {
+  return new MediaSessionClientTest(new GingleSessionTestParser(),
+                                    cricket::PROTOCOL_GINGLE);
+}
+
+MediaSessionClientTest* JingleTest() {
+  return new MediaSessionClientTest(new JingleSessionTestParser(),
+                                    cricket::PROTOCOL_JINGLE);
+}
+
+TEST(MediaSessionTest, JingleGoodVideoInitiate) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestGoodIncomingInitiate(kJingleVideoInitiate, elem.use());
+  test->TestCodecsOfVideoInitiate(elem.get());
+}
+
+TEST(MediaSessionTest, JingleGoodVideoInitiateWithBandwidth) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->ExpectVideoBandwidth(42000);
+  test->TestGoodIncomingInitiate(kJingleVideoInitiateWithBandwidth, elem.use());
+}
+
+TEST(MediaSessionTest, JingleGoodVideoInitiateWithRtcpMux) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->ExpectVideoRtcpMux(true);
+  test->TestGoodIncomingInitiate(kJingleVideoInitiateWithRtcpMux, elem.use());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateAllSupportedAudioCodecs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestGoodIncomingInitiate(kJingleInitiate, elem.use());
+  test->TestHasAllSupportedAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateDifferentPreferenceAudioCodecs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestGoodIncomingInitiate(kJingleInitiateDifferentPreference,
+      elem.use());
+  test->TestHasAllSupportedAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateSomeUnsupportedAudioCodecs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestGoodIncomingInitiate(kJingleInitiateSomeUnsupported, elem.use());
+  test->TestHasAudioCodecsFromInitiateSomeUnsupported(elem.get());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateDynamicAudioCodecs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestGoodIncomingInitiate(kJingleInitiateDynamicAudioCodecs, elem.use());
+  test->TestHasAudioCodecsFromInitiateDynamicAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateStaticAudioCodecs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestGoodIncomingInitiate(kJingleInitiateStaticAudioCodecs, elem.use());
+  test->TestHasAudioCodecsFromInitiateStaticAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, JingleBadInitiateNoAudioCodecs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateNoAudioCodecs);
+}
+
+TEST(MediaSessionTest, JingleBadInitiateNoSupportedAudioCodecs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateNoSupportedAudioCodecs);
+}
+
+TEST(MediaSessionTest, JingleBadInitiateWrongClockrates) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateWrongClockrates);
+}
+
+TEST(MediaSessionTest, JingleBadInitiateWrongChannels) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateWrongChannels);
+}
+
+TEST(MediaSessionTest, JingleBadInitiateNoPayloadTypes) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateNoPayloadTypes);
+}
+
+TEST(MediaSessionTest, JingleBadInitiateDynamicWithoutNames) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateDynamicWithoutNames);
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiate) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithBandwidth) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options;
+  options.has_video = true;
+  options.video_bandwidth = 42000;
+  test->TestGoodOutgoingInitiate(options);
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithRtcpMux) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options;
+  options.has_video = true;
+  options.rtcp_mux_enabled = true;
+  test->TestGoodOutgoingInitiate(options);
+}
+
+// Crypto related tests.
+
+// Offer has crypto but the session is not secured, just ignore it.
+TEST(MediaSessionTest, JingleInitiateWithCryptoIsIgnoredWhenNotSecured) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestGoodIncomingInitiate(AddEncryption(kJingleVideoInitiate,
+                                              kJingleCryptoOffer), elem.use());
+}
+
+// Offer has crypto required but the session is not secure, fail.
+TEST(MediaSessionTest, JingleInitiateWithCryptoRequiredWhenNotSecured) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(AddEncryption(kJingleVideoInitiate,
+                                             kJingleRequiredCryptoOffer));
+}
+
+// Offer has no crypto but the session is secure required, fail.
+TEST(MediaSessionTest, JingleInitiateWithNoCryptoFailsWhenSecureRequired) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->ExpectCrypto(cricket::SEC_REQUIRED);
+  test->TestBadIncomingInitiate(kJingleInitiate);
+}
+
+// Offer has crypto and session is secure, expect crypto in the answer.
+TEST(MediaSessionTest, JingleInitiateWithCryptoWhenSecureEnabled) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->ExpectCrypto(cricket::SEC_ENABLED);
+  test->TestGoodIncomingInitiate(AddEncryption(kJingleVideoInitiate,
+                                              kJingleCryptoOffer), elem.use());
+}
+
+// Offer has crypto and session is secure required, expect crypto in
+// the answer.
+TEST(MediaSessionTest, JingleInitiateWithCryptoWhenSecureRequired) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->ExpectCrypto(cricket::SEC_REQUIRED);
+  test->TestGoodIncomingInitiate(AddEncryption(kJingleVideoInitiate,
+                                              kJingleCryptoOffer), elem.use());
+}
+
+// Offer has unsupported crypto and session is secure, no crypto in
+// the answer.
+TEST(MediaSessionTest, JingleInitiateWithUnsupportedCrypto) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->MakeSignalingSecure(cricket::SEC_ENABLED);
+  test->TestGoodIncomingInitiate(
+      AddEncryption(kJingleInitiate, kJingleUnsupportedCryptoOffer),
+      elem.use());
+}
+
+// Offer has unsupported REQUIRED crypto and session is not secure, fail.
+TEST(MediaSessionTest, JingleInitiateWithRequiredUnsupportedCrypto) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(
+      AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
+}
+
+// Offer has unsupported REQUIRED crypto and session is secure, fail.
+TEST(MediaSessionTest, JingleInitiateWithRequiredUnsupportedCryptoWhenSecure) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->MakeSignalingSecure(cricket::SEC_ENABLED);
+  test->TestBadIncomingInitiate(
+      AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
+}
+
+// Offer has unsupported REQUIRED crypto and session is required secure, fail.
+TEST(MediaSessionTest,
+     JingleInitiateWithRequiredUnsupportedCryptoWhenSecureRequired) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->MakeSignalingSecure(cricket::SEC_REQUIRED);
+  test->TestBadIncomingInitiate(
+      AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
+}
+
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithCrypto) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->ExpectCrypto(cricket::SEC_ENABLED);
+  test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithCryptoRequired) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->ExpectCrypto(cricket::SEC_REQUIRED);
+  test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, JingleIncomingAcceptWithSsrcs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestIncomingAcceptWithSsrcs(kJingleAcceptWithSsrcs);
+}
+
+TEST(MediaSessionTest, JingleStreamsUpdateAndView) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestStreamsUpdateAndViewRequests();
+}
+
+// Gingle tests
+
+TEST(MediaSessionTest, GingleGoodVideoInitiate) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(kGingleVideoInitiate, elem.use());
+  test->TestCodecsOfVideoInitiate(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodVideoInitiateWithBandwidth) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectVideoBandwidth(42000);
+  test->TestGoodIncomingInitiate(kGingleVideoInitiateWithBandwidth, elem.use());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateAllSupportedAudioCodecs) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(kGingleInitiate, elem.use());
+  test->TestHasAllSupportedAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateAllSupportedAudioCodecsWithCrypto) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectCrypto(cricket::SEC_ENABLED);
+  test->TestGoodIncomingInitiate(AddEncryption(kGingleInitiate,
+                                              kGingleCryptoOffer), elem.use());
+  test->TestHasAllSupportedAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateDifferentPreferenceAudioCodecs) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(kGingleInitiateDifferentPreference,
+      elem.use());
+  test->TestHasAllSupportedAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateSomeUnsupportedAudioCodecs) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(kGingleInitiateSomeUnsupported, elem.use());
+  test->TestHasAudioCodecsFromInitiateSomeUnsupported(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateDynamicAudioCodecs) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(kGingleInitiateDynamicAudioCodecs, elem.use());
+  test->TestHasAudioCodecsFromInitiateDynamicAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateStaticAudioCodecs) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(kGingleInitiateStaticAudioCodecs, elem.use());
+  test->TestHasAudioCodecsFromInitiateStaticAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateNoAudioCodecs) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(kGingleInitiateNoAudioCodecs, elem.use());
+  test->TestHasDefaultAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleBadInitiateNoSupportedAudioCodecs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(kGingleInitiateNoSupportedAudioCodecs);
+}
+
+TEST(MediaSessionTest, GingleBadInitiateWrongClockrates) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(kGingleInitiateWrongClockrates);
+}
+
+TEST(MediaSessionTest, GingleBadInitiateWrongChannels) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(kGingleInitiateWrongChannels);
+}
+
+
+TEST(MediaSessionTest, GingleBadInitiateNoPayloadTypes) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(kGingleInitiateNoPayloadTypes);
+}
+
+TEST(MediaSessionTest, GingleBadInitiateDynamicWithoutNames) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(kGingleInitiateDynamicWithoutNames);
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiate) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithBandwidth) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  cricket::CallOptions options;
+  options.has_video = true;
+  options.video_bandwidth = 42000;
+  test->TestGoodOutgoingInitiate(options);
+}
+
+// Crypto related tests.
+
+// Offer has crypto but the session is not secured, just ignore it.
+TEST(MediaSessionTest, GingleInitiateWithCryptoIsIgnoredWhenNotSecured) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(AddEncryption(kGingleInitiate,
+                                              kGingleCryptoOffer), elem.use());
+}
+
+// Offer has crypto required but the session is not secure, fail.
+TEST(MediaSessionTest, GingleInitiateWithCryptoRequiredWhenNotSecured) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(AddEncryption(kGingleInitiate,
+                                             kGingleRequiredCryptoOffer));
+}
+
+// Offer has no crypto but the session is secure required, fail.
+TEST(MediaSessionTest, GingleInitiateWithNoCryptoFailsWhenSecureRequired) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectCrypto(cricket::SEC_REQUIRED);
+  test->TestBadIncomingInitiate(kGingleInitiate);
+}
+
+// Offer has crypto and session is secure, expect crypto in the answer.
+TEST(MediaSessionTest, GingleInitiateWithCryptoWhenSecureEnabled) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectCrypto(cricket::SEC_ENABLED);
+  test->TestGoodIncomingInitiate(AddEncryption(kGingleInitiate,
+                                              kGingleCryptoOffer), elem.use());
+}
+
+// Offer has crypto and session is secure required, expect crypto in
+// the answer.
+TEST(MediaSessionTest, GingleInitiateWithCryptoWhenSecureRequired) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectCrypto(cricket::SEC_REQUIRED);
+  test->TestGoodIncomingInitiate(AddEncryption(kGingleInitiate,
+                                              kGingleCryptoOffer), elem.use());
+}
+
+// Offer has unsupported crypto and session is secure, no crypto in
+// the answer.
+TEST(MediaSessionTest, GingleInitiateWithUnsupportedCrypto) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->MakeSignalingSecure(cricket::SEC_ENABLED);
+  test->TestGoodIncomingInitiate(
+      AddEncryption(kGingleInitiate, kGingleUnsupportedCryptoOffer),
+      elem.use());
+}
+
+// Offer has unsupported REQUIRED crypto and session is not secure, fail.
+TEST(MediaSessionTest, GingleInitiateWithRequiredUnsupportedCrypto) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(
+      AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
+}
+
+// Offer has unsupported REQUIRED crypto and session is secure, fail.
+TEST(MediaSessionTest, GingleInitiateWithRequiredUnsupportedCryptoWhenSecure) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->MakeSignalingSecure(cricket::SEC_ENABLED);
+  test->TestBadIncomingInitiate(
+      AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
+}
+
+// Offer has unsupported REQUIRED crypto and session is required secure, fail.
+TEST(MediaSessionTest,
+     GingleInitiateWithRequiredUnsupportedCryptoWhenSecureRequired) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->MakeSignalingSecure(cricket::SEC_REQUIRED);
+  test->TestBadIncomingInitiate(
+      AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithCrypto) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectCrypto(cricket::SEC_ENABLED);
+  test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithCryptoRequired) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectCrypto(cricket::SEC_REQUIRED);
+  test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, GingleIncomingAcceptWithSsrcs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestIncomingAcceptWithSsrcs(kGingleAcceptWithSsrcs);
+}
diff --git a/talk/session/phone/nullvideorenderer.h b/talk/session/phone/nullvideorenderer.h
new file mode 100644
index 0000000..aa48205
--- /dev/null
+++ b/talk/session/phone/nullvideorenderer.h
@@ -0,0 +1,48 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_NULLVIDEORENDERER_H_
+#define TALK_SESSION_PHONE_NULLVIDEORENDERER_H_
+
+#include "talk/session/phone/videorenderer.h"
+
+namespace cricket {
+
+// Simple implementation for use in tests.
+class NullVideoRenderer : public VideoRenderer {
+  virtual bool SetSize(int width, int height, int reserved) {
+    return true;
+  }
+  // Called when a new frame is available for display.
+  virtual bool RenderFrame(const VideoFrame *frame) {
+    return true;
+  }
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_NULLVIDEORENDERER_H_
diff --git a/talk/session/phone/rtcpmuxfilter.cc b/talk/session/phone/rtcpmuxfilter.cc
index 8654214..1ca71e2 100644
--- a/talk/session/phone/rtcpmuxfilter.cc
+++ b/talk/session/phone/rtcpmuxfilter.cc
@@ -35,21 +35,22 @@
 }
 
 bool RtcpMuxFilter::IsActive() const {
-  // We can receive muxed media prior to the accept, so we have to be able to
-  // deal with that.
-  return (state_ == ST_SENTOFFER || state_ == ST_ACTIVE);
+  return state_ == ST_ACTIVE;
 }
 
 bool RtcpMuxFilter::SetOffer(bool offer_enable, ContentSource source) {
-  bool ret = false;
-  if (state_ == ST_INIT) {
-    offer_enable_ = offer_enable;
-    state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
-    ret = true;
-  } else {
-    LOG(LS_ERROR) << "Invalid state for RTCP mux offer";
+  // Allow to SetOffer in |state_| ST_INIT. Also allow to call SetOffer in
+  // |state_| ST_ACTIVE if |offer_enable| is true.
+  // We can't disable the filter once it has been activated.
+  if ((state_ != ST_INIT && state_ != ST_ACTIVE) ||
+      (state_ == ST_ACTIVE  && offer_enable == false)) {
+    LOG(LS_ERROR) << "Invalid state for change of RTCP mux offer";
+    return false;
   }
-  return ret;
+
+  offer_enable_ = offer_enable;
+  state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+  return true;
 }
 
 bool RtcpMuxFilter::SetAnswer(bool answer_enable, ContentSource source) {
@@ -68,6 +69,9 @@
         LOG(LS_WARNING) << "Invalid parameters in RTCP mux answer";
       }
     }
+  } else if (state_ == ST_ACTIVE && answer_enable == offer_enable_) {
+    // It is ok as long as the answer state matches the current filter state.
+    ret = true;
   } else {
     LOG(LS_ERROR) << "Invalid state for RTCP mux answer";
   }
@@ -81,7 +85,7 @@
   // http://tools.ietf.org/html/rfc5761.
   // Note that if we offer RTCP mux, we may receive muxed RTCP before we
   // receive the answer, so we operate in that state too.
-  if (!IsActive()) {
+  if (state_ != ST_SENTOFFER && state_ != ST_ACTIVE) {
     return false;
   }
 
diff --git a/talk/session/phone/rtcpmuxfilter_unittest.cc b/talk/session/phone/rtcpmuxfilter_unittest.cc
new file mode 100644
index 0000000..6a4c0e3
--- /dev/null
+++ b/talk/session/phone/rtcpmuxfilter_unittest.cc
@@ -0,0 +1,128 @@
+// 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/session/phone/rtcpmuxfilter.h"
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/testutils.h"
+
+TEST(RtcpMuxFilterTest, DemuxRtcpSender) {
+  cricket::RtcpMuxFilter filter;
+  const char data[] = { 0, 73, 0, 0 };
+  const int len = 4;
+
+  // Init state - refuse to demux
+  EXPECT_FALSE(filter.DemuxRtcp(data, len));
+  // After sent offer, demux should be enabled
+  filter.SetOffer(true, cricket::CS_LOCAL);
+  EXPECT_TRUE(filter.DemuxRtcp(data, len));
+  // Remote accepted, demux should be enabled
+  filter.SetAnswer(true, cricket::CS_REMOTE);
+  EXPECT_TRUE(filter.DemuxRtcp(data, len));
+}
+
+TEST(RtcpMuxFilterTest, DemuxRtcpReceiver) {
+  cricket::RtcpMuxFilter filter;
+  const char data[] = { 0, 73, 0, 0 };
+  const int len = 4;
+
+  // Init state - refuse to demux
+  EXPECT_FALSE(filter.DemuxRtcp(data, len));
+  // After received offer, demux should not be enabled
+  filter.SetOffer(true, cricket::CS_REMOTE);
+  EXPECT_FALSE(filter.DemuxRtcp(data, len));
+  // We accept, demux is now enabled.
+  filter.SetAnswer(true, cricket::CS_LOCAL);
+  EXPECT_TRUE(filter.DemuxRtcp(data, len));
+}
+
+TEST(RtcpMuxFilterTest, IsActiveSender) {
+  cricket::RtcpMuxFilter filter;
+  // Init state - not active.
+  EXPECT_FALSE(filter.IsActive());
+  // After sent offer, demux should not be active
+  filter.SetOffer(true, cricket::CS_LOCAL);
+  EXPECT_FALSE(filter.IsActive());
+  // Remote accepted, filter is now active
+  filter.SetAnswer(true, cricket::CS_REMOTE);
+  EXPECT_TRUE(filter.IsActive());
+}
+
+TEST(RtcpMuxFilterTest, IsActiveReceiver) {
+  cricket::RtcpMuxFilter filter;
+  // Init state - not active.
+  EXPECT_FALSE(filter.IsActive());
+  // After received offer, demux should not be active
+  filter.SetOffer(true, cricket::CS_REMOTE);
+  EXPECT_FALSE(filter.IsActive());
+  // We accept, filter is now active
+  filter.SetAnswer(true, cricket::CS_LOCAL);
+  EXPECT_TRUE(filter.IsActive());
+}
+
+// Test that we can enable the filter in an update.
+// We can not disable the filter later since that would mean we need to
+// recreate a rtcp transport channel.
+TEST(RtcpMuxFilterTest, EnableFilterDuringUpdate) {
+  cricket::RtcpMuxFilter filter;
+  EXPECT_FALSE(filter.IsActive());
+  EXPECT_TRUE(filter.SetOffer(false, cricket::CS_REMOTE));
+  EXPECT_TRUE(filter.SetAnswer(false, cricket::CS_LOCAL));
+  EXPECT_FALSE(filter.IsActive());
+
+  EXPECT_TRUE(filter.SetOffer(true, cricket::CS_REMOTE));
+  EXPECT_TRUE(filter.SetAnswer(true, cricket::CS_LOCAL));
+  EXPECT_TRUE(filter.IsActive());
+
+  EXPECT_FALSE(filter.SetOffer(false, cricket::CS_REMOTE));
+  EXPECT_FALSE(filter.SetAnswer(false, cricket::CS_LOCAL));
+  EXPECT_TRUE(filter.IsActive());
+}
+
+// Test that the filter can be enabled twice.
+TEST(RtcpMuxFilterTest, EnableFilterTwiceDuringUpdate) {
+  cricket::RtcpMuxFilter filter;
+
+  EXPECT_TRUE(filter.SetOffer(true, cricket::CS_REMOTE));
+  EXPECT_TRUE(filter.SetAnswer(true, cricket::CS_LOCAL));
+  EXPECT_TRUE(filter.IsActive());
+
+  EXPECT_TRUE(filter.SetOffer(true, cricket::CS_REMOTE));
+  EXPECT_TRUE(filter.SetAnswer(true, cricket::CS_LOCAL));
+  EXPECT_TRUE(filter.IsActive());
+}
+
+// Test that the filter can be kept disabled during updates.
+TEST(RtcpMuxFilterTest, KeepFilterDisabledDuringUpdate) {
+  cricket::RtcpMuxFilter filter;
+
+  EXPECT_TRUE(filter.SetOffer(false, cricket::CS_REMOTE));
+  EXPECT_TRUE(filter.SetAnswer(false, cricket::CS_LOCAL));
+  EXPECT_FALSE(filter.IsActive());
+
+  EXPECT_TRUE(filter.SetOffer(false, cricket::CS_REMOTE));
+  EXPECT_TRUE(filter.SetAnswer(false, cricket::CS_LOCAL));
+  EXPECT_FALSE(filter.IsActive());
+}
diff --git a/talk/session/phone/rtpdump.cc b/talk/session/phone/rtpdump.cc
index 50ebd2a..2db2990 100644
--- a/talk/session/phone/rtpdump.cc
+++ b/talk/session/phone/rtpdump.cc
@@ -33,7 +33,7 @@
 
 #include "talk/base/byteorder.h"
 #include "talk/base/logging.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 #include "talk/session/phone/rtputils.h"
 
 namespace cricket {
diff --git a/talk/session/phone/rtpdump_unittest.cc b/talk/session/phone/rtpdump_unittest.cc
new file mode 100644
index 0000000..fc48858
--- /dev/null
+++ b/talk/session/phone/rtpdump_unittest.cc
@@ -0,0 +1,289 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/rtpdump.h"
+#include "talk/session/phone/rtputils.h"
+#include "talk/session/phone/testutils.h"
+
+namespace cricket {
+
+static const uint32 kTestSsrc = 1;
+
+// Test that we read the correct header fields from the RTP/RTCP packet.
+TEST(RtpDumpTest, ReadRtpDumpPacket) {
+  talk_base::ByteBuffer rtp_buf;
+  RtpTestUtility::kTestRawRtpPackets[0].WriteToByteBuffer(kTestSsrc, &rtp_buf);
+  RtpDumpPacket rtp_packet(rtp_buf.Data(), rtp_buf.Length(), 0, false);
+
+  int type;
+  int seq_num;
+  uint32 ts;
+  uint32 ssrc;
+  EXPECT_TRUE(rtp_packet.IsValidRtpPacket());
+  EXPECT_FALSE(rtp_packet.IsValidRtcpPacket());
+  EXPECT_TRUE(rtp_packet.GetRtpPayloadType(&type));
+  EXPECT_EQ(0, type);
+  EXPECT_TRUE(rtp_packet.GetRtpSeqNum(&seq_num));
+  EXPECT_EQ(0, seq_num);
+  EXPECT_TRUE(rtp_packet.GetRtpTimestamp(&ts));
+  EXPECT_EQ(0U, ts);
+  EXPECT_TRUE(rtp_packet.GetRtpSsrc(&ssrc));
+  EXPECT_EQ(kTestSsrc, ssrc);
+  EXPECT_FALSE(rtp_packet.GetRtcpType(&type));
+
+  talk_base::ByteBuffer rtcp_buf;
+  RtpTestUtility::kTestRawRtcpPackets[0].WriteToByteBuffer(&rtcp_buf);
+  RtpDumpPacket rtcp_packet(rtcp_buf.Data(), rtcp_buf.Length(), 0, true);
+
+  EXPECT_FALSE(rtcp_packet.IsValidRtpPacket());
+  EXPECT_TRUE(rtcp_packet.IsValidRtcpPacket());
+  EXPECT_TRUE(rtcp_packet.GetRtcpType(&type));
+  EXPECT_EQ(0, type);
+}
+
+// Test that we read only the RTP dump file.
+TEST(RtpDumpTest, ReadRtpDumpFile) {
+  RtpDumpPacket packet;
+  talk_base::MemoryStream stream;
+  RtpDumpWriter writer(&stream);
+  talk_base::scoped_ptr<RtpDumpReader> reader;
+
+  // Write a RTP packet to the stream, which is a valid RTP dump. Next, we will
+  // change the first line to make the RTP dump valid or invalid.
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(1, false, kTestSsrc, &writer));
+  stream.Rewind();
+  reader.reset(new RtpDumpReader(&stream));
+  EXPECT_EQ(talk_base::SR_SUCCESS, reader->ReadPacket(&packet));
+
+  // The first line is correct.
+  stream.Rewind();
+  const char new_line[] = "#!rtpplay1.0 1.1.1.1/1\n";
+  EXPECT_EQ(talk_base::SR_SUCCESS,
+            stream.WriteAll(new_line, strlen(new_line), NULL, NULL));
+  stream.Rewind();
+  reader.reset(new RtpDumpReader(&stream));
+  EXPECT_EQ(talk_base::SR_SUCCESS, reader->ReadPacket(&packet));
+
+  // The first line is not correct: not started with #!rtpplay1.0.
+  stream.Rewind();
+  const char new_line2[] = "#!rtpplaz1.0 0.0.0.0/0\n";
+  EXPECT_EQ(talk_base::SR_SUCCESS,
+            stream.WriteAll(new_line2, strlen(new_line2), NULL, NULL));
+  stream.Rewind();
+  reader.reset(new RtpDumpReader(&stream));
+  EXPECT_EQ(talk_base::SR_ERROR, reader->ReadPacket(&packet));
+
+  // The first line is not correct: no port.
+  stream.Rewind();
+  const char new_line3[] = "#!rtpplay1.0 0.0.0.0//\n";
+  EXPECT_EQ(talk_base::SR_SUCCESS,
+            stream.WriteAll(new_line3, strlen(new_line3), NULL, NULL));
+  stream.Rewind();
+  reader.reset(new RtpDumpReader(&stream));
+  EXPECT_EQ(talk_base::SR_ERROR, reader->ReadPacket(&packet));
+}
+
+// Test that we read the same RTP packets that rtp dump writes.
+TEST(RtpDumpTest, WriteReadSameRtp) {
+  talk_base::MemoryStream stream;
+  RtpDumpWriter writer(&stream);
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+      RtpTestUtility::GetTestPacketCount(), false, kTestSsrc, &writer));
+  EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+      RtpTestUtility::GetTestPacketCount(), &stream, kTestSsrc));
+
+  // Check stream has only RtpTestUtility::GetTestPacketCount() packets.
+  RtpDumpPacket packet;
+  RtpDumpReader reader(&stream);
+  for (size_t i = 0; i < RtpTestUtility::GetTestPacketCount(); ++i) {
+    EXPECT_EQ(talk_base::SR_SUCCESS , reader.ReadPacket(&packet));
+    uint32 ssrc;
+    EXPECT_TRUE(GetRtpSsrc(&packet.data[0], packet.data.size(), &ssrc));
+    EXPECT_EQ(kTestSsrc, ssrc);
+  }
+  // No more packets to read.
+  EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+
+  // Rewind the stream and read again with a specified ssrc.
+  stream.Rewind();
+  RtpDumpReader reader_w_ssrc(&stream);
+  const uint32 send_ssrc = kTestSsrc + 1;
+  reader_w_ssrc.SetSsrc(send_ssrc);
+  for (size_t i = 0; i < RtpTestUtility::GetTestPacketCount(); ++i) {
+    EXPECT_EQ(talk_base::SR_SUCCESS , reader_w_ssrc.ReadPacket(&packet));
+    uint32 ssrc;
+    EXPECT_TRUE(GetRtpSsrc(&packet.data[0], packet.data.size(), &ssrc));
+    EXPECT_EQ(send_ssrc, ssrc);
+  }
+  // No more packets to read.
+  EXPECT_EQ(talk_base::SR_EOS, reader_w_ssrc.ReadPacket(&packet));
+}
+
+// Test that we read the same RTCP packets that rtp dump writes.
+TEST(RtpDumpTest, WriteReadSameRtcp) {
+  talk_base::MemoryStream stream;
+  RtpDumpWriter writer(&stream);
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+      RtpTestUtility::GetTestPacketCount(), true, kTestSsrc, &writer));
+  EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+      RtpTestUtility::GetTestPacketCount(), &stream, kTestSsrc));
+
+  // Check stream has only RtpTestUtility::GetTestPacketCount() packets.
+  RtpDumpPacket packet;
+  RtpDumpReader reader(&stream);
+  reader.SetSsrc(kTestSsrc + 1);  // Does not affect RTCP packet.
+  for (size_t i = 0; i < RtpTestUtility::GetTestPacketCount(); ++i) {
+    EXPECT_EQ(talk_base::SR_SUCCESS , reader.ReadPacket(&packet));
+  }
+  // No more packets to read.
+  EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+}
+
+// Test dumping only RTP packet headers.
+TEST(RtpDumpTest, WriteReadRtpHeadersOnly) {
+  talk_base::MemoryStream stream;
+  RtpDumpWriter writer(&stream);
+  writer.set_packet_filter(PF_RTPHEADER);
+
+  // Write some RTP and RTCP packets. RTP packets should only have headers;
+  // RTCP packets should be eaten.
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+      RtpTestUtility::GetTestPacketCount(), false, kTestSsrc, &writer));
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+      RtpTestUtility::GetTestPacketCount(), true, kTestSsrc, &writer));
+  stream.Rewind();
+
+  // Check that only RTP packet headers are present.
+  RtpDumpPacket packet;
+  RtpDumpReader reader(&stream);
+  for (size_t i = 0; i < RtpTestUtility::GetTestPacketCount(); ++i) {
+    EXPECT_EQ(talk_base::SR_SUCCESS , reader.ReadPacket(&packet));
+    EXPECT_FALSE(packet.is_rtcp);
+    size_t len = 0;
+    packet.GetRtpHeaderLen(&len);
+    EXPECT_EQ(len, packet.data.size());
+  }
+  // No more packets to read.
+  EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+}
+
+// Test dumping only RTCP packets.
+TEST(RtpDumpTest, WriteReadRtcpOnly) {
+  talk_base::MemoryStream stream;
+  RtpDumpWriter writer(&stream);
+  writer.set_packet_filter(PF_RTCPPACKET);
+
+  // Write some RTP and RTCP packets. RTP packets should be eaten.
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+      RtpTestUtility::GetTestPacketCount(), false, kTestSsrc, &writer));
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+      RtpTestUtility::GetTestPacketCount(), true, kTestSsrc, &writer));
+  stream.Rewind();
+
+  // Check that only RTCP packets are present.
+  RtpDumpPacket packet;
+  RtpDumpReader reader(&stream);
+  for (size_t i = 0; i < RtpTestUtility::GetTestPacketCount(); ++i) {
+    EXPECT_EQ(talk_base::SR_SUCCESS , reader.ReadPacket(&packet));
+    EXPECT_TRUE(packet.is_rtcp);
+  }
+  // No more packets to read.
+  EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+}
+
+// Test that RtpDumpLoopReader reads RTP packets continously and the elapsed
+// time, the sequence number, and timestamp are maintained properly.
+TEST(RtpDumpTest, LoopReadRtp) {
+  talk_base::MemoryStream stream;
+  RtpDumpWriter writer(&stream);
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+      RtpTestUtility::GetTestPacketCount(), false, kTestSsrc, &writer));
+  EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+      3 * RtpTestUtility::GetTestPacketCount(), &stream, kTestSsrc));
+}
+
+// Test that RtpDumpLoopReader reads RTCP packets continously and the elapsed
+// time is maintained properly.
+TEST(RtpDumpTest, LoopReadRtcp) {
+  talk_base::MemoryStream stream;
+  RtpDumpWriter writer(&stream);
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+      RtpTestUtility::GetTestPacketCount(), true, kTestSsrc, &writer));
+  EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+      3 * RtpTestUtility::GetTestPacketCount(), &stream, kTestSsrc));
+}
+
+// Test that RtpDumpLoopReader reads continously from stream with a single RTP
+// packets.
+TEST(RtpDumpTest, LoopReadSingleRtp) {
+  talk_base::MemoryStream stream;
+  RtpDumpWriter writer(&stream);
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(1, false, kTestSsrc, &writer));
+
+  // The regular reader can read only one packet.
+  RtpDumpPacket packet;
+  stream.Rewind();
+  RtpDumpReader reader(&stream);
+  EXPECT_EQ(talk_base::SR_SUCCESS, reader.ReadPacket(&packet));
+  EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+
+  // The loop reader reads three packets from the input stream.
+  stream.Rewind();
+  RtpDumpLoopReader loop_reader(&stream);
+  EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+  EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+  EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+}
+
+// Test that RtpDumpLoopReader reads continously from stream with a single RTCP
+// packets.
+TEST(RtpDumpTest, LoopReadSingleRtcp) {
+  talk_base::MemoryStream stream;
+  RtpDumpWriter writer(&stream);
+  ASSERT_TRUE(RtpTestUtility::WriteTestPackets(1, true, kTestSsrc, &writer));
+
+  // The regular reader can read only one packet.
+  RtpDumpPacket packet;
+  stream.Rewind();
+  RtpDumpReader reader(&stream);
+  EXPECT_EQ(talk_base::SR_SUCCESS, reader.ReadPacket(&packet));
+  EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+
+  // The loop reader reads three packets from the input stream.
+  stream.Rewind();
+  RtpDumpLoopReader loop_reader(&stream);
+  EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+  EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+  EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/rtputils.cc b/talk/session/phone/rtputils.cc
index e37c390..ded84a5 100644
--- a/talk/session/phone/rtputils.cc
+++ b/talk/session/phone/rtputils.cc
@@ -77,5 +77,18 @@
   return true;
 }
 
-}  // namespace cricket
+// This method returns SSRC first of RTCP packet, except if packet is SDES.
+// TODO - Fully implement RFC 5506. This standard doesn't restrict
+// to send non-compound packets only to feedback messages.
+bool GetRtcpSsrc(const void* data, size_t len, uint32* value) {
+  // Packet should be at least of 8 bytes, to get SSRC from a RTCP packet.
+  if (!data || len < kMinRtcpPacketLen + 4 || !value) return false;
+  int pl_type;
+  if (!GetRtcpType(data, len, &pl_type)) return false;
+  // SDES packet parsing is not supported.
+  if (pl_type == kRtcpTypeSDES) return false;
+  *value = talk_base::GetBE32(static_cast<const uint8*>(data) + 4);
+  return true;
+}
 
+}  // namespace cricket
diff --git a/talk/session/phone/rtputils.h b/talk/session/phone/rtputils.h
index 69daa83..bc2aaff 100644
--- a/talk/session/phone/rtputils.h
+++ b/talk/session/phone/rtputils.h
@@ -36,12 +36,23 @@
 const size_t kMaxRtpPacketLen = 2048;
 const size_t kMinRtcpPacketLen = 4;
 
+enum RtcpTypes {
+  kRtcpTypeSR = 200,      // Sender report payload type.
+  kRtcpTypeRR = 201,      // Receiver report payload type.
+  kRtcpTypeSDES = 202,    // SDES payload type.
+  kRtcpTypeBye = 203,     // BYE payload type.
+  kRtcpTypeApp = 204,     // APP payload type.
+  kRtcpTypeRTPFB = 205,   // Transport layer Feedback message payload type.
+  kRtcpTypePSFB = 206,    // Payload-specific Feedback message payload type.
+};
+
 bool GetRtpPayloadType(const void* data, size_t len, int* value);
 bool GetRtpSeqNum(const void* data, size_t len, int* value);
 bool GetRtpTimestamp(const void* data, size_t len, uint32* value);
 bool GetRtpSsrc(const void* data, size_t len, uint32* value);
 bool GetRtpHeaderLen(const void* data, size_t len, size_t* value);
 bool GetRtcpType(const void* data, size_t len, int* value);
+bool GetRtcpSsrc(const void* data, size_t len, uint32* value);
 
 }  // namespace cricket
 
diff --git a/talk/session/phone/rtputils_unittest.cc b/talk/session/phone/rtputils_unittest.cc
new file mode 100644
index 0000000..b1efd0c
--- /dev/null
+++ b/talk/session/phone/rtputils_unittest.cc
@@ -0,0 +1,144 @@
+/*
+ * 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/session/phone/fakertp.h"
+#include "talk/session/phone/rtputils.h"
+
+namespace cricket {
+
+static const unsigned char kRtpPacketWithMarker[] = {
+    0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+};
+// 3 CSRCs (0x01020304, 0x12345678, 0xAABBCCDD)
+// Extension (0xBEDE, 0x1122334455667788)
+static const unsigned char kRtpPacketWithMarkerAndCsrcAndExtension[] = {
+    0x93, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+    0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD,
+    0xBE, 0xDE, 0x00, 0x02, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88
+};
+static const unsigned char kInvalidPacket[] = { 0x80, 0x00 };
+static const unsigned char kInvalidPacketWithCsrc[] = {
+    0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+    0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC
+};
+static const unsigned char kInvalidPacketWithCsrcAndExtension1[] = {
+    0x93, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+    0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD,
+    0xBE, 0xDE, 0x00
+};
+static const unsigned char kInvalidPacketWithCsrcAndExtension2[] = {
+    0x93, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+    0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD,
+    0xBE, 0xDE, 0x00, 0x02, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
+};
+
+// PT = 206, FMT = 1, Sender SSRC  = 0x1111, Media SSRC = 0x1111
+// No FCI information is needed for PLI.
+static const unsigned char kNonCompoundRtcpPliFeedbackPacket[] = {
+    0x81, 0xCE, 0x00, 0x0C, 0x00, 0x00, 0x11, 0x11, 0x00, 0x00, 0x11, 0x11
+};
+
+// Packet has only mandatory fixed RTCP header
+// PT = 204, SSRC = 0x1111
+static const unsigned char kNonCompoundRtcpAppPacket[] = {
+    0x81, 0xCC, 0x00, 0x0C, 0x00, 0x00, 0x11, 0x11
+};
+
+// PT = 202, Source count = 0
+static const unsigned char kNonCompoundRtcpSDESPacket[] = {
+    0x80, 0xCA, 0x00, 0x00
+};
+
+TEST(RtpUtilsTest, GetRtp) {
+  int pt;
+  EXPECT_TRUE(GetRtpPayloadType(kPcmuFrame, sizeof(kPcmuFrame), &pt));
+  EXPECT_EQ(0, pt);
+  EXPECT_TRUE(GetRtpPayloadType(kRtpPacketWithMarker,
+                                sizeof(kRtpPacketWithMarker), &pt));
+  EXPECT_EQ(0, pt);
+
+  int seq_num;
+  EXPECT_TRUE(GetRtpSeqNum(kPcmuFrame, sizeof(kPcmuFrame), &seq_num));
+  EXPECT_EQ(1, seq_num);
+
+  uint32 ts;
+  EXPECT_TRUE(GetRtpTimestamp(kPcmuFrame, sizeof(kPcmuFrame), &ts));
+  EXPECT_EQ(0u, ts);
+
+  uint32 ssrc;
+  EXPECT_TRUE(GetRtpSsrc(kPcmuFrame, sizeof(kPcmuFrame), &ssrc));
+  EXPECT_EQ(1u, ssrc);
+
+  EXPECT_FALSE(GetRtpPayloadType(kInvalidPacket, sizeof(kInvalidPacket), &pt));
+  EXPECT_FALSE(GetRtpSeqNum(kInvalidPacket, sizeof(kInvalidPacket), &seq_num));
+  EXPECT_FALSE(GetRtpTimestamp(kInvalidPacket, sizeof(kInvalidPacket), &ts));
+  EXPECT_FALSE(GetRtpSsrc(kInvalidPacket, sizeof(kInvalidPacket), &ssrc));
+}
+
+TEST(RtpUtilsTest, GetRtpHeaderLen) {
+  size_t len;
+  EXPECT_TRUE(GetRtpHeaderLen(kPcmuFrame, sizeof(kPcmuFrame), &len));
+  EXPECT_EQ(12U, len);
+
+  EXPECT_TRUE(GetRtpHeaderLen(kRtpPacketWithMarkerAndCsrcAndExtension,
+                              sizeof(kRtpPacketWithMarkerAndCsrcAndExtension),
+                              &len));
+  EXPECT_EQ(sizeof(kRtpPacketWithMarkerAndCsrcAndExtension), len);
+
+  EXPECT_FALSE(GetRtpHeaderLen(kInvalidPacket, sizeof(kInvalidPacket), &len));
+  EXPECT_FALSE(GetRtpHeaderLen(kInvalidPacketWithCsrc,
+                               sizeof(kInvalidPacketWithCsrc), &len));
+  EXPECT_FALSE(GetRtpHeaderLen(kInvalidPacketWithCsrcAndExtension1,
+                               sizeof(kInvalidPacketWithCsrcAndExtension1),
+                               &len));
+  EXPECT_FALSE(GetRtpHeaderLen(kInvalidPacketWithCsrcAndExtension2,
+                               sizeof(kInvalidPacketWithCsrcAndExtension2),
+                               &len));
+}
+
+TEST(RtpUtilsTest, GetRtcp) {
+  int pt;
+  EXPECT_TRUE(GetRtcpType(kRtcpReport, sizeof(kRtcpReport), &pt));
+  EXPECT_EQ(0xc9, pt);
+
+  EXPECT_FALSE(GetRtcpType(kInvalidPacket, sizeof(kInvalidPacket), &pt));
+
+  uint32 ssrc;
+  EXPECT_TRUE(GetRtcpSsrc(kNonCompoundRtcpPliFeedbackPacket,
+                          sizeof(kNonCompoundRtcpPliFeedbackPacket),
+                          &ssrc));
+  EXPECT_TRUE(GetRtcpSsrc(kNonCompoundRtcpAppPacket,
+                          sizeof(kNonCompoundRtcpAppPacket),
+                          &ssrc));
+  EXPECT_FALSE(GetRtcpSsrc(kNonCompoundRtcpSDESPacket,
+                           sizeof(kNonCompoundRtcpSDESPacket),
+                           &ssrc));
+}
+
+}  // namespace cricket
+
diff --git a/talk/session/phone/screencastid.h b/talk/session/phone/screencastid.h
new file mode 100644
index 0000000..0acc7de
--- /dev/null
+++ b/talk/session/phone/screencastid.h
@@ -0,0 +1,88 @@
+// Copyright 2012 Google Inc. All Rights Reserved
+
+//
+// Defines variant class ScreencastId that combines WindowId and DesktopId.
+
+#ifndef TALK_SESSION_PHONE_SCREENCASTID_H_
+#define TALK_SESSION_PHONE_SCREENCASTID_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/window.h"
+#include "talk/base/windowpicker.h"
+
+namespace cricket {
+
+class ScreencastId;
+typedef std::vector<ScreencastId> ScreencastIdList;
+
+// Used for identifying a window or desktop to be screencast.
+class ScreencastId {
+ public:
+  enum Type { INVALID, WINDOW, DESKTOP };
+
+  // Default constructor indicates invalid ScreencastId.
+  ScreencastId() : type_(INVALID) {}
+  explicit ScreencastId(const talk_base::WindowId& id)
+      : type_(WINDOW), window_(id) {
+  }
+  explicit ScreencastId(const talk_base::DesktopId& id)
+      : type_(DESKTOP), desktop_(id) {
+  }
+
+  Type type() const { return type_; }
+  const talk_base::WindowId& window() const { return window_; }
+  const talk_base::DesktopId& desktop() const { return desktop_; }
+
+  // Title is an optional parameter.
+  const std::string& title() const { return title_; }
+  void set_title(const std::string& desc) { title_ = desc; }
+
+  bool IsValid() const {
+    if (type_ == INVALID) {
+      return false;
+    } else if (type_ == WINDOW) {
+      return window_.IsValid();
+    } else {
+      return desktop_.IsValid();
+    }
+  }
+  bool IsWindow() const { return type_ == WINDOW; }
+  bool IsDesktop() const { return type_ == DESKTOP; }
+  bool EqualsId(const ScreencastId& other) const {
+    if (type_ != other.type_) {
+      return false;
+    }
+    if (type_ == INVALID) {
+      return true;
+    } else if (type_ == WINDOW) {
+      return window_.Equals(other.window());
+    }
+    return desktop_.Equals(other.desktop());
+  }
+
+  // T is assumed to be WindowDescription or DesktopDescription.
+  template<class T>
+  static cricket::ScreencastIdList Convert(const std::vector<T>& list) {
+    ScreencastIdList screencast_list;
+    screencast_list.reserve(list.size());
+    for (typename std::vector<T>::const_iterator it = list.begin();
+         it != list.end(); ++it) {
+      ScreencastId id(it->id());
+      id.set_title(it->title());
+      screencast_list.push_back(id);
+    }
+    return screencast_list;
+  }
+
+ private:
+  Type type_;
+  talk_base::WindowId window_;
+  talk_base::DesktopId desktop_;
+  std::string title_;  // Optional.
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_SCREENCASTID_H_
diff --git a/talk/session/phone/srtpfilter.cc b/talk/session/phone/srtpfilter.cc
index b3578e1..7a1cb78 100644
--- a/talk/session/phone/srtpfilter.cc
+++ b/talk/session/phone/srtpfilter.cc
@@ -25,22 +25,8 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-// talk's config.h, generated from mac_config_dot_h for OSX, conflicts with the
-// one included by the libsrtp headers. Don't use it. Instead, we keep HAVE_SRTP
-// and LOGGING defined in config.h.
 #undef HAVE_CONFIG_H
 
-#ifdef OSX
-// TODO: For the XCode build, we force SRTP (b/2500074)
-#ifndef HAVE_SRTP
-#define HAVE_SRTP 1
-#endif  // HAVE_SRTP
-// If LOGGING is not defined, define it to 1 (b/3245816)
-#ifndef LOGGING
-#define LOGGING 1
-#endif  // HAVE_SRTP
-#endif
-
 #include "talk/session/phone/srtpfilter.h"
 
 #include <algorithm>
@@ -48,7 +34,8 @@
 
 #include "talk/base/base64.h"
 #include "talk/base/logging.h"
-#include "talk/base/time.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/timeutils.h"
 #include "talk/session/phone/rtputils.h"
 
 // Enable this line to turn on SRTP debugging
@@ -79,6 +66,8 @@
 const char CS_AES_CM_128_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80";
 const char CS_AES_CM_128_HMAC_SHA1_32[] = "AES_CM_128_HMAC_SHA1_32";
 const int SRTP_MASTER_KEY_BASE64_LEN = SRTP_MASTER_KEY_LEN * 4 / 3;
+const int SRTP_MASTER_KEY_KEY_LEN = 16;
+const int SRTP_MASTER_KEY_SALT_LEN = 14;
 
 #ifndef HAVE_SRTP
 
@@ -110,44 +99,41 @@
 
 SrtpFilter::SrtpFilter()
     : state_(ST_INIT),
-      send_session_(new SrtpSession()),
-      recv_session_(new SrtpSession()) {
-  SignalSrtpError.repeat(send_session_->SignalSrtpError);
-  SignalSrtpError.repeat(recv_session_->SignalSrtpError);
+      signal_silent_time_in_ms_(0) {
 }
 
 SrtpFilter::~SrtpFilter() {
 }
 
 bool SrtpFilter::IsActive() const {
-  return (state_ == ST_ACTIVE);
+  return state_ >= ST_ACTIVE;
 }
 
 bool SrtpFilter::SetOffer(const std::vector<CryptoParams>& offer_params,
                           ContentSource source) {
-  bool ret = false;
-  if (state_ == ST_INIT) {
-    ret = StoreParams(offer_params, source);
-  } else {
-    LOG(LS_ERROR) << "Invalid state for SRTP offer";
+  if (state_ != ST_INIT && state_ != ST_ACTIVE) {
+    LOG(LS_ERROR) << "Wrong state to update SRTP offer";
+    return false;
   }
-  return ret;
+  return StoreParams(offer_params, source);
 }
 
 bool SrtpFilter::SetAnswer(const std::vector<CryptoParams>& answer_params,
                            ContentSource source) {
   bool ret = false;
   if ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
-      (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL)) {
+      (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL) ||
+      (state_ == ST_SENTUPDATEDOFFER && source == CS_REMOTE) ||
+      (state_ == ST_RECEIVEDUPDATEDOFFER && source == CS_LOCAL)) {
     // If the answer requests crypto, finalize the parameters and apply them.
     // Otherwise, complete the negotiation of a unencrypted session.
     if (!answer_params.empty()) {
       CryptoParams selected_params;
       ret = NegotiateParams(answer_params, &selected_params);
       if (ret) {
-        if (state_ == ST_SENTOFFER) {
+        if (state_ == ST_SENTOFFER || state_ == ST_SENTUPDATEDOFFER) {
           ret = ApplyParams(selected_params, answer_params[0]);
-        } else {  // ST_RECEIVEDOFFER
+        } else {  // ST_RECEIVEDOFFER || ST_RECEIVEDUPDATEDOFFER
           ret = ApplyParams(answer_params[0], selected_params);
         }
       }
@@ -160,6 +146,68 @@
   return ret;
 }
 
+bool SrtpFilter::SetRtpParams(const std::string& send_cs,
+                              const uint8* send_key, int send_key_len,
+                              const std::string& recv_cs,
+                              const uint8* recv_key, int recv_key_len) {
+  if (state_ == ST_ACTIVE) {
+    LOG(LS_ERROR) << "Tried to set SRTP Params when filter already active";
+    return false;
+  }
+  CreateSrtpSessions();
+  if (!send_session_->SetSend(send_cs, send_key, send_key_len))
+    return false;
+
+  if (!recv_session_->SetRecv(recv_cs, recv_key, recv_key_len))
+    return false;
+
+  state_ = ST_ACTIVE;
+
+  LOG(LS_INFO) << "SRTP activated with negotiated parameters:"
+               << " send cipher_suite " << send_cs
+               << " recv cipher_suite " << recv_cs;
+
+  return true;
+}
+
+// This function is provided separately because DTLS-SRTP behaves
+// differently in RTP/RTCP mux and non-mux modes.
+//
+// - In the non-muxed case, RTP and RTCP are keyed with different
+//   keys (from different DTLS handshakes), and so we need a new
+//   SrtpSession.
+// - In the muxed case, they are keyed with the same keys, so
+//   this function is not needed
+bool SrtpFilter::SetRtcpParams(const std::string& send_cs,
+                               const uint8* send_key, int send_key_len,
+                               const std::string& recv_cs,
+                               const uint8* recv_key, int recv_key_len) {
+  // This can only be called once, but can be safely called after
+  // SetRtpParams
+  if (send_rtcp_session_.get() || send_rtcp_session_.get()) {
+    LOG(LS_ERROR) << "Tried to set SRTCP Params when filter already active";
+    return false;
+  }
+
+  send_rtcp_session_.reset(new SrtpSession());
+  SignalSrtpError.repeat(send_rtcp_session_->SignalSrtpError);
+  send_rtcp_session_->set_signal_silent_time(signal_silent_time_in_ms_);
+  if (!send_rtcp_session_->SetRecv(send_cs, send_key, send_key_len))
+    return false;
+
+  recv_rtcp_session_.reset(new SrtpSession());
+  SignalSrtpError.repeat(recv_rtcp_session_->SignalSrtpError);
+  recv_rtcp_session_->set_signal_silent_time(signal_silent_time_in_ms_);
+  if (!recv_rtcp_session_->SetRecv(recv_cs, recv_key, recv_key_len))
+    return false;
+
+  LOG(LS_INFO) << "SRTCP activated with negotiated parameters:"
+               << " send cipher_suite " << send_cs
+               << " recv cipher_suite " << recv_cs;
+
+  return true;
+}
+
 bool SrtpFilter::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
   if (!IsActive()) {
     LOG(LS_WARNING) << "Failed to ProtectRtp: SRTP not active";
@@ -173,7 +221,11 @@
     LOG(LS_WARNING) << "Failed to ProtectRtcp: SRTP not active";
     return false;
   }
-  return send_session_->ProtectRtcp(p, in_len, max_len, out_len);
+  if (send_rtcp_session_.get()) {
+    return send_rtcp_session_->ProtectRtcp(p, in_len, max_len, out_len);
+  } else {
+    return send_session_->ProtectRtcp(p, in_len, max_len, out_len);
+  }
 }
 
 bool SrtpFilter::UnprotectRtp(void* p, int in_len, int* out_len) {
@@ -189,21 +241,47 @@
     LOG(LS_WARNING) << "Failed to UnprotectRtcp: SRTP not active";
     return false;
   }
-  return recv_session_->UnprotectRtcp(p, in_len, out_len);
+  if (recv_rtcp_session_.get()) {
+    return recv_rtcp_session_->UnprotectRtcp(p, in_len, out_len);
+  } else {
+    return recv_session_->UnprotectRtcp(p, in_len, out_len);
+  }
 }
 
 void SrtpFilter::set_signal_silent_time(uint32 signal_silent_time_in_ms) {
-  send_session_->set_signal_silent_time(signal_silent_time_in_ms);
-  recv_session_->set_signal_silent_time(signal_silent_time_in_ms);
+  signal_silent_time_in_ms_ = signal_silent_time_in_ms;
+  if (state_ == ST_ACTIVE) {
+    send_session_->set_signal_silent_time(signal_silent_time_in_ms);
+    recv_session_->set_signal_silent_time(signal_silent_time_in_ms);
+    if (send_rtcp_session_.get())
+      send_rtcp_session_->set_signal_silent_time(signal_silent_time_in_ms);
+    if (recv_rtcp_session_.get())
+      recv_rtcp_session_->set_signal_silent_time(signal_silent_time_in_ms);
+  }
 }
 
 bool SrtpFilter::StoreParams(const std::vector<CryptoParams>& params,
                              ContentSource source) {
   offer_params_ = params;
-  state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+  if (state_ == ST_INIT) {
+    state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+  } else {  // ST_ACTIVE
+    state_ =
+        (source == CS_LOCAL) ? ST_SENTUPDATEDOFFER : ST_RECEIVEDUPDATEDOFFER;
+  }
   return true;
 }
 
+void SrtpFilter::CreateSrtpSessions() {
+  send_session_.reset(new SrtpSession());
+  recv_session_.reset(new SrtpSession());
+  SignalSrtpError.repeat(send_session_->SignalSrtpError);
+  SignalSrtpError.repeat(recv_session_->SignalSrtpError);
+
+  send_session_->set_signal_silent_time(signal_silent_time_in_ms_);
+  recv_session_->set_signal_silent_time(signal_silent_time_in_ms_);
+}
+
 bool SrtpFilter::NegotiateParams(const std::vector<CryptoParams>& answer_params,
                                  CryptoParams* selected_params) {
   // We're processing an accept. We should have exactly one set of params,
@@ -239,6 +317,7 @@
   ret = (ParseKeyParams(send_params.key_params, send_key, sizeof(send_key)) &&
          ParseKeyParams(recv_params.key_params, recv_key, sizeof(recv_key)));
   if (ret) {
+    CreateSrtpSessions();
     ret = (send_session_->SetSend(send_params.cipher_suite,
                                   send_key, sizeof(send_key)) &&
            recv_session_->SetRecv(recv_params.cipher_suite,
@@ -290,7 +369,6 @@
 #ifdef HAVE_SRTP
 
 bool SrtpSession::inited_ = false;
-std::list<SrtpSession*> SrtpSession::sessions_;
 
 SrtpSession::SrtpSession()
     : session_(NULL),
@@ -298,12 +376,12 @@
       rtcp_auth_tag_len_(0),
       srtp_stat_(new SrtpStat()),
       last_send_seq_num_(-1) {
-  sessions_.push_back(this);
+  sessions()->push_back(this);
   SignalSrtpError.repeat(srtp_stat_->SignalSrtpError);
 }
 
 SrtpSession::~SrtpSession() {
-  sessions_.erase(std::find(sessions_.begin(), sessions_.end(), this));
+  sessions()->erase(std::find(sessions()->begin(), sessions()->end(), this));
   if (session_) {
     srtp_dealloc(session_);
   }
@@ -503,8 +581,8 @@
 }
 
 void SrtpSession::HandleEventThunk(srtp_event_data_t* ev) {
-  for (std::list<SrtpSession*>::iterator it = sessions_.begin();
-       it != sessions_.end(); ++it) {
+  for (std::list<SrtpSession*>::iterator it = sessions()->begin();
+       it != sessions()->end(); ++it) {
     if ((*it)->session_ == ev->session) {
       (*it)->HandleEvent(ev);
       break;
@@ -512,8 +590,14 @@
   }
 }
 
+std::list<SrtpSession*>* SrtpSession::sessions() {
+  LIBJINGLE_DEFINE_STATIC_LOCAL(std::list<SrtpSession*>, sessions, ());
+  return &sessions;
+}
+
 #else   // !HAVE_SRTP
 
+// On some systems, SRTP is not (yet) available.
 
 SrtpSession::SrtpSession() {
   LOG(WARNING) << "SRTP implementation is missing.";
@@ -628,6 +712,7 @@
 
 #else   // !HAVE_SRTP
 
+// On some systems, SRTP is not (yet) available.
 
 SrtpStat::SrtpStat()
     : signal_silent_time_(1000) {
diff --git a/talk/session/phone/srtpfilter.h b/talk/session/phone/srtpfilter.h
index 409a3a9..991d4bf 100644
--- a/talk/session/phone/srtpfilter.h
+++ b/talk/session/phone/srtpfilter.h
@@ -57,6 +57,10 @@
 // Key is 128 bits and salt is 112 bits == 30 bytes. B64 bloat => 40 bytes.
 extern const int SRTP_MASTER_KEY_BASE64_LEN;
 
+// Needed for DTLS-SRTP
+extern const int SRTP_MASTER_KEY_KEY_LEN;
+extern const int SRTP_MASTER_KEY_SALT_LEN;
+
 class SrtpSession;
 class SrtpStat;
 
@@ -99,6 +103,17 @@
   bool SetAnswer(const std::vector<CryptoParams>& answer_params,
                  ContentSource source);
 
+  // Just set up both sets of keys directly.
+  // Used with DTLS-SRTP.
+  bool SetRtpParams(const std::string& send_cs,
+                    const uint8* send_key, int send_key_len,
+                    const std::string& recv_cs,
+                    const uint8* recv_key, int recv_key_len);
+  bool SetRtcpParams(const std::string& send_cs,
+                     const uint8* send_key, int send_key_len,
+                     const std::string& recv_cs,
+                     const uint8* recv_key, int recv_key_len);
+
   // Encrypts/signs an individual RTP/RTCP packet, in-place.
   // If an HMAC is used, this will increase the packet size.
   bool ProtectRtp(void* data, int in_len, int max_len, int* out_len);
@@ -114,8 +129,9 @@
   sigslot::repeater3<uint32, Mode, Error> SignalSrtpError;
 
  protected:
-  bool StoreParams(const std::vector<CryptoParams>& offer_params,
+  bool StoreParams(const std::vector<CryptoParams>& params,
                    ContentSource source);
+  void CreateSrtpSessions();
   bool NegotiateParams(const std::vector<CryptoParams>& answer_params,
                        CryptoParams* selected_params);
   bool ApplyParams(const CryptoParams& send_params,
@@ -124,11 +140,25 @@
   static bool ParseKeyParams(const std::string& params, uint8* key, int len);
 
  private:
-  enum State { ST_INIT, ST_SENTOFFER, ST_RECEIVEDOFFER, ST_ACTIVE };
+  enum State {
+    ST_INIT,           // SRTP filter unused.
+    ST_SENTOFFER,      // Offer with SRTP parameters sent.
+    ST_RECEIVEDOFFER,  // Offer with SRTP parameters received.
+    ST_ACTIVE,         // Offer and answer set.
+    // SRTP filter is active but new parameters are offered.
+    // When the answer is set, the state transitions to ST_ACTIVE or ST_INIT.
+    ST_SENTUPDATEDOFFER,
+    // SRTP filter is active but new parameters are received.
+    // When the answer is set, the state transitions back to ST_ACTIVE.
+    ST_RECEIVEDUPDATEDOFFER
+  };
   State state_;
+  uint32 signal_silent_time_in_ms_;
   std::vector<CryptoParams> offer_params_;
   talk_base::scoped_ptr<SrtpSession> send_session_;
   talk_base::scoped_ptr<SrtpSession> recv_session_;
+  talk_base::scoped_ptr<SrtpSession> send_rtcp_session_;
+  talk_base::scoped_ptr<SrtpSession> recv_rtcp_session_;
 };
 
 // Class that wraps a libSRTP session.
@@ -164,13 +194,13 @@
   static bool Init();
   void HandleEvent(const srtp_event_data_t* ev);
   static void HandleEventThunk(srtp_event_data_t* ev);
+  static std::list<SrtpSession*>* sessions();
 
   srtp_t session_;
   int rtp_auth_tag_len_;
   int rtcp_auth_tag_len_;
   talk_base::scoped_ptr<SrtpStat> srtp_stat_;
   static bool inited_;
-  static std::list<SrtpSession*> sessions_;
   int last_send_seq_num_;
   DISALLOW_COPY_AND_ASSIGN(SrtpSession);
 };
@@ -228,7 +258,7 @@
     FailureStat()
         : last_signal_time(0) {
     }
-    FailureStat(uint32 in_last_signal_time)
+    explicit FailureStat(uint32 in_last_signal_time)
         : last_signal_time(in_last_signal_time) {
     }
     void Reset() {
diff --git a/talk/session/phone/srtpfilter_unittest.cc b/talk/session/phone/srtpfilter_unittest.cc
new file mode 100644
index 0000000..276c341
--- /dev/null
+++ b/talk/session/phone/srtpfilter_unittest.cc
@@ -0,0 +1,764 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/byteorder.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/session/phone/fakertp.h"
+#include "talk/session/phone/srtpfilter.h"
+#ifdef SRTP_RELATIVE_PATH
+#include "crypto/include/err.h"
+#else
+#include "third_party/libsrtp/crypto/include/err.h"
+#endif
+
+using cricket::CS_AES_CM_128_HMAC_SHA1_80;
+using cricket::CS_AES_CM_128_HMAC_SHA1_32;
+using cricket::CryptoParams;
+using cricket::CS_LOCAL;
+using cricket::CS_REMOTE;
+
+static const uint8 kTestKey1[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234";
+static const uint8 kTestKey2[] = "4321ZYXWVUTSRQPONMLKJIHGFEDCBA";
+static const int kTestKeyLen = 30;
+static const std::string kTestKeyParams1 =
+    "inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz";
+static const std::string kTestKeyParams2 =
+    "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR";
+static const std::string kTestKeyParams3 =
+    "inline:1234X19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz";
+static const std::string kTestKeyParams4 =
+    "inline:4567QCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR";
+static const cricket::CryptoParams kTestCryptoParams1(
+    1, "AES_CM_128_HMAC_SHA1_80", kTestKeyParams1, "");
+static const cricket::CryptoParams kTestCryptoParams2(
+    1, "AES_CM_128_HMAC_SHA1_80", kTestKeyParams2, "");
+
+static int rtp_auth_tag_len(const std::string& cs) {
+  return (cs == CS_AES_CM_128_HMAC_SHA1_32) ? 4 : 10;
+}
+static int rtcp_auth_tag_len(const std::string& cs) {
+  return 10;
+}
+
+class SrtpFilterTest : public testing::Test {
+ protected:
+  SrtpFilterTest()
+  // Need to initialize |sequence_number_|, the value does not matter.
+      : sequence_number_(1) {
+  }
+  static std::vector<CryptoParams> MakeVector(const CryptoParams& params) {
+    std::vector<CryptoParams> vec;
+    vec.push_back(params);
+    return vec;
+  }
+  void TestSetParams(const std::vector<CryptoParams>& params1,
+                     const std::vector<CryptoParams>& params2) {
+    EXPECT_TRUE(f1_.SetOffer(params1, CS_LOCAL));
+    EXPECT_TRUE(f2_.SetOffer(params1, CS_REMOTE));
+    EXPECT_TRUE(f2_.SetAnswer(params2, CS_LOCAL));
+    EXPECT_TRUE(f1_.SetAnswer(params2, CS_REMOTE));
+    EXPECT_TRUE(f1_.IsActive());
+  }
+  void TestProtectUnprotect(const std::string& cs1, const std::string& cs2) {
+    char rtp_packet[sizeof(kPcmuFrame) + 10];
+    char original_rtp_packet[sizeof(kPcmuFrame)];
+    char rtcp_packet[sizeof(kRtcpReport) + 4 + 10];
+    int rtp_len = sizeof(kPcmuFrame), rtcp_len = sizeof(kRtcpReport), out_len;
+    memcpy(rtp_packet, kPcmuFrame, rtp_len);
+    // In order to be able to run this test function multiple times we can not
+    // use the same sequence number twice. Increase the sequence number by one.
+    talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet) + 2,
+                       ++sequence_number_);
+    memcpy(original_rtp_packet, rtp_packet, rtp_len);
+    memcpy(rtcp_packet, kRtcpReport, rtcp_len);
+
+    EXPECT_TRUE(f1_.ProtectRtp(rtp_packet, rtp_len,
+                               sizeof(rtp_packet), &out_len));
+    EXPECT_EQ(out_len, rtp_len + rtp_auth_tag_len(cs1));
+    EXPECT_NE(0, memcmp(rtp_packet, original_rtp_packet, rtp_len));
+    EXPECT_TRUE(f2_.UnprotectRtp(rtp_packet, out_len, &out_len));
+    EXPECT_EQ(rtp_len, out_len);
+    EXPECT_EQ(0, memcmp(rtp_packet, original_rtp_packet, rtp_len));
+
+    EXPECT_TRUE(f2_.ProtectRtp(rtp_packet, rtp_len,
+                               sizeof(rtp_packet), &out_len));
+    EXPECT_EQ(out_len, rtp_len + rtp_auth_tag_len(cs2));
+    EXPECT_NE(0, memcmp(rtp_packet, original_rtp_packet, rtp_len));
+    EXPECT_TRUE(f1_.UnprotectRtp(rtp_packet, out_len, &out_len));
+    EXPECT_EQ(rtp_len, out_len);
+    EXPECT_EQ(0, memcmp(rtp_packet, original_rtp_packet, rtp_len));
+
+    EXPECT_TRUE(f1_.ProtectRtcp(rtcp_packet, rtcp_len,
+                                sizeof(rtcp_packet), &out_len));
+    EXPECT_EQ(out_len, rtcp_len + 4 + rtcp_auth_tag_len(cs1));  // NOLINT
+    EXPECT_NE(0, memcmp(rtcp_packet, kRtcpReport, rtcp_len));
+    EXPECT_TRUE(f2_.UnprotectRtcp(rtcp_packet, out_len, &out_len));
+    EXPECT_EQ(rtcp_len, out_len);
+    EXPECT_EQ(0, memcmp(rtcp_packet, kRtcpReport, rtcp_len));
+
+    EXPECT_TRUE(f2_.ProtectRtcp(rtcp_packet, rtcp_len,
+                                sizeof(rtcp_packet), &out_len));
+    EXPECT_EQ(out_len, rtcp_len + 4 + rtcp_auth_tag_len(cs2));  // NOLINT
+    EXPECT_NE(0, memcmp(rtcp_packet, kRtcpReport, rtcp_len));
+    EXPECT_TRUE(f1_.UnprotectRtcp(rtcp_packet, out_len, &out_len));
+    EXPECT_EQ(rtcp_len, out_len);
+    EXPECT_EQ(0, memcmp(rtcp_packet, kRtcpReport, rtcp_len));
+  }
+  cricket::SrtpFilter f1_;
+  cricket::SrtpFilter f2_;
+  int sequence_number_;
+};
+
+// Test that we can set up the session and keys properly.
+TEST_F(SrtpFilterTest, TestGoodSetupOneCipherSuite) {
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+}
+
+// Test that we can set up things with multiple params.
+TEST_F(SrtpFilterTest, TestGoodSetupMultipleCipherSuites) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  offer.push_back(kTestCryptoParams1);
+  offer[1].tag = 2;
+  offer[1].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+  answer[0].tag = 2;
+  answer[0].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+}
+
+// Test that we handle the cases where crypto is not desired.
+TEST_F(SrtpFilterTest, TestGoodSetupNoCipherSuites) {
+  std::vector<CryptoParams> offer, answer;
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we handle the cases where crypto is not desired by the remote side.
+TEST_F(SrtpFilterTest, TestGoodSetupNoAnswerCipherSuites) {
+  std::vector<CryptoParams> answer;
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we call the functions the wrong way.
+TEST_F(SrtpFilterTest, TestBadSetup) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetOffer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we have params in the answer when none were offered.
+TEST_F(SrtpFilterTest, TestNoAnswerCipherSuites) {
+  std::vector<CryptoParams> offer;
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we have too many params in our answer.
+TEST_F(SrtpFilterTest, TestMultipleAnswerCipherSuites) {
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer.push_back(kTestCryptoParams2);
+  answer[1].tag = 2;
+  answer[1].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we don't support the cipher-suite.
+TEST_F(SrtpFilterTest, TestInvalidCipherSuite) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  offer[0].cipher_suite = answer[0].cipher_suite = "FOO";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we can't agree on a tag.
+TEST_F(SrtpFilterTest, TestNoMatchingTag) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].tag = 99;
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we can't agree on a cipher-suite.
+TEST_F(SrtpFilterTest, TestNoMatchingCipherSuite) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].tag = 2;
+  answer[0].cipher_suite = "FOO";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys with bad base64 content.
+TEST_F(SrtpFilterTest, TestInvalidKeyData) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].key_params = "inline:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys with the wrong key-method.
+TEST_F(SrtpFilterTest, TestWrongKeyMethod) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].key_params = "outline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys of the wrong length.
+TEST_F(SrtpFilterTest, TestKeyTooShort) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].key_params = "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtx";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys of the wrong length.
+TEST_F(SrtpFilterTest, TestKeyTooLong) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].key_params = "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBRABCD";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys with lifetime or MKI set (since we don't support)
+TEST_F(SrtpFilterTest, TestUnsupportedOptions) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  answer[0].key_params =
+      "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:4";
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we can encrypt/decrypt after negotiating AES_CM_128_HMAC_SHA1_80.
+TEST_F(SrtpFilterTest, TestProtect_AES_CM_128_HMAC_SHA1_80) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  offer.push_back(kTestCryptoParams1);
+  offer[1].tag = 2;
+  offer[1].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+  TestSetParams(offer, answer);
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// Test that we can encrypt/decrypt after negotiating AES_CM_128_HMAC_SHA1_32.
+TEST_F(SrtpFilterTest, TestProtect_AES_CM_128_HMAC_SHA1_32) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+  offer.push_back(kTestCryptoParams1);
+  offer[1].tag = 2;
+  offer[1].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+  answer[0].tag = 2;
+  answer[0].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+  TestSetParams(offer, answer);
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_32, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Test that we can change encryption parameters.
+TEST_F(SrtpFilterTest, TestChangeParameters) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+
+  TestSetParams(offer, answer);
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+
+  // Change the key parameters and cipher_suite.
+  offer[0].key_params = kTestKeyParams3;
+  offer[0].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+  answer[0].key_params = kTestKeyParams4;
+  answer[0].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f1_.IsActive());
+
+  // Test that the old keys are valid until the negotiation is complete.
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+
+  // Complete the negotiation and test that we can still understand each other.
+  EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_32, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Test that we can disable encryption.
+TEST_F(SrtpFilterTest, TestDisableEncryption) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+
+  TestSetParams(offer, answer);
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+
+  offer.clear();
+  answer.clear();
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+
+  // Test that the old keys are valid until the negotiation is complete.
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+
+  // Complete the negotiation.
+  EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_FALSE(f2_.IsActive());
+}
+
+// Test directly setting the params with AES_CM_128_HMAC_SHA1_80
+TEST_F(SrtpFilterTest, TestProtect_SetParamsDirect_AES_CM_128_HMAC_SHA1_80) {
+  EXPECT_TRUE(f1_.SetRtpParams(CS_AES_CM_128_HMAC_SHA1_80,
+                               kTestKey1, kTestKeyLen,
+                               CS_AES_CM_128_HMAC_SHA1_80,
+                               kTestKey2, kTestKeyLen));
+  EXPECT_TRUE(f2_.SetRtpParams(CS_AES_CM_128_HMAC_SHA1_80,
+                               kTestKey2, kTestKeyLen,
+                               CS_AES_CM_128_HMAC_SHA1_80,
+                               kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(f1_.SetRtcpParams(CS_AES_CM_128_HMAC_SHA1_80,
+                                kTestKey1, kTestKeyLen,
+                                CS_AES_CM_128_HMAC_SHA1_80,
+                                kTestKey2, kTestKeyLen));
+  EXPECT_TRUE(f2_.SetRtcpParams(CS_AES_CM_128_HMAC_SHA1_80,
+                                kTestKey2, kTestKeyLen,
+                                CS_AES_CM_128_HMAC_SHA1_80,
+                                kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// Test directly setting the params with AES_CM_128_HMAC_SHA1_32
+TEST_F(SrtpFilterTest, TestProtect_SetParamsDirect_AES_CM_128_HMAC_SHA1_32) {
+  EXPECT_TRUE(f1_.SetRtpParams(CS_AES_CM_128_HMAC_SHA1_32,
+                               kTestKey1, kTestKeyLen,
+                               CS_AES_CM_128_HMAC_SHA1_32,
+                               kTestKey2, kTestKeyLen));
+  EXPECT_TRUE(f2_.SetRtpParams(CS_AES_CM_128_HMAC_SHA1_32,
+                               kTestKey2, kTestKeyLen,
+                               CS_AES_CM_128_HMAC_SHA1_32,
+                               kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(f1_.SetRtcpParams(CS_AES_CM_128_HMAC_SHA1_32,
+                                kTestKey1, kTestKeyLen,
+                                CS_AES_CM_128_HMAC_SHA1_32,
+                                kTestKey2, kTestKeyLen));
+  EXPECT_TRUE(f2_.SetRtcpParams(CS_AES_CM_128_HMAC_SHA1_32,
+                                kTestKey2, kTestKeyLen,
+                                CS_AES_CM_128_HMAC_SHA1_32,
+                                kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_32, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Test directly setting the params with bogus keys
+TEST_F(SrtpFilterTest, TestSetParamsKeyTooShort) {
+  EXPECT_FALSE(f1_.SetRtpParams(CS_AES_CM_128_HMAC_SHA1_80,
+                                kTestKey1, kTestKeyLen - 1,
+                                CS_AES_CM_128_HMAC_SHA1_80,
+                                kTestKey1, kTestKeyLen - 1));
+  EXPECT_FALSE(f1_.SetRtcpParams(CS_AES_CM_128_HMAC_SHA1_80,
+                                 kTestKey1, kTestKeyLen - 1,
+                                 CS_AES_CM_128_HMAC_SHA1_80,
+                                 kTestKey1, kTestKeyLen - 1));
+}
+
+class SrtpSessionTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    rtp_len_ = sizeof(kPcmuFrame);
+    rtcp_len_ = sizeof(kRtcpReport);
+    memcpy(rtp_packet_, kPcmuFrame, rtp_len_);
+    memcpy(rtcp_packet_, kRtcpReport, rtcp_len_);
+  }
+  void TestProtectRtp(const std::string& cs) {
+    int out_len = 0;
+    EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_,
+                               sizeof(rtp_packet_), &out_len));
+    EXPECT_EQ(out_len, rtp_len_ + rtp_auth_tag_len(cs));
+    EXPECT_NE(0, memcmp(rtp_packet_, kPcmuFrame, rtp_len_));
+    rtp_len_ = out_len;
+  }
+  void TestProtectRtcp(const std::string& cs) {
+    int out_len = 0;
+    EXPECT_TRUE(s1_.ProtectRtcp(rtcp_packet_, rtcp_len_,
+                                sizeof(rtcp_packet_), &out_len));
+    EXPECT_EQ(out_len, rtcp_len_ + 4 + rtcp_auth_tag_len(cs));  // NOLINT
+    EXPECT_NE(0, memcmp(rtcp_packet_, kRtcpReport, rtcp_len_));
+    rtcp_len_ = out_len;
+  }
+  void TestUnprotectRtp(const std::string& cs) {
+    int out_len = 0, expected_len = sizeof(kPcmuFrame);
+    EXPECT_TRUE(s2_.UnprotectRtp(rtp_packet_, rtp_len_, &out_len));
+    EXPECT_EQ(expected_len, out_len);
+    EXPECT_EQ(0, memcmp(rtp_packet_, kPcmuFrame, out_len));
+  }
+  void TestUnprotectRtcp(const std::string& cs) {
+    int out_len = 0, expected_len = sizeof(kRtcpReport);
+    EXPECT_TRUE(s2_.UnprotectRtcp(rtcp_packet_, rtcp_len_, &out_len));
+    EXPECT_EQ(expected_len, out_len);
+    EXPECT_EQ(0, memcmp(rtcp_packet_, kRtcpReport, out_len));
+  }
+  cricket::SrtpSession s1_;
+  cricket::SrtpSession s2_;
+  char rtp_packet_[sizeof(kPcmuFrame) + 10];
+  char rtcp_packet_[sizeof(kRtcpReport) + 4 + 10];
+  int rtp_len_;
+  int rtcp_len_;
+};
+
+// Test that we can set up the session and keys properly.
+TEST_F(SrtpSessionTest, TestGoodSetup) {
+  EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+}
+
+// Test that we can't change the keys once set.
+TEST_F(SrtpSessionTest, TestBadSetup) {
+  EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  EXPECT_FALSE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey2, kTestKeyLen));
+  EXPECT_FALSE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey2, kTestKeyLen));
+}
+
+// Test that we fail keys of the wrong length.
+TEST_F(SrtpSessionTest, TestKeysTooShort) {
+  EXPECT_FALSE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, 1));
+  EXPECT_FALSE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, 1));
+}
+
+// Test that we can encrypt and decrypt RTP/RTCP using AES_CM_128_HMAC_SHA1_80.
+TEST_F(SrtpSessionTest, TestProtect_AES_CM_128_HMAC_SHA1_80) {
+  EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  TestProtectRtp(CS_AES_CM_128_HMAC_SHA1_80);
+  TestProtectRtcp(CS_AES_CM_128_HMAC_SHA1_80);
+  TestUnprotectRtp(CS_AES_CM_128_HMAC_SHA1_80);
+  TestUnprotectRtcp(CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// Test that we can encrypt and decrypt RTP/RTCP using AES_CM_128_HMAC_SHA1_32.
+TEST_F(SrtpSessionTest, TestProtect_AES_CM_128_HMAC_SHA1_32) {
+  EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_32, kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_32, kTestKey1, kTestKeyLen));
+  TestProtectRtp(CS_AES_CM_128_HMAC_SHA1_32);
+  TestProtectRtcp(CS_AES_CM_128_HMAC_SHA1_32);
+  TestUnprotectRtp(CS_AES_CM_128_HMAC_SHA1_32);
+  TestUnprotectRtcp(CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Test that we fail to unprotect if someone tampers with the RTP/RTCP paylaods.
+TEST_F(SrtpSessionTest, TestTamperReject) {
+  int out_len;
+  EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  TestProtectRtp(CS_AES_CM_128_HMAC_SHA1_80);
+  TestProtectRtcp(CS_AES_CM_128_HMAC_SHA1_80);
+  rtp_packet_[0] = 0x12;
+  rtcp_packet_[1] = 0x34;
+  EXPECT_FALSE(s2_.UnprotectRtp(rtp_packet_, rtp_len_, &out_len));
+  EXPECT_FALSE(s2_.UnprotectRtcp(rtcp_packet_, rtcp_len_, &out_len));
+}
+
+// Test that we fail to unprotect if the payloads are not authenticated.
+TEST_F(SrtpSessionTest, TestUnencryptReject) {
+  int out_len;
+  EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  EXPECT_FALSE(s2_.UnprotectRtp(rtp_packet_, rtp_len_, &out_len));
+  EXPECT_FALSE(s2_.UnprotectRtcp(rtcp_packet_, rtcp_len_, &out_len));
+}
+
+// Test that we fail when using buffers that are too small.
+TEST_F(SrtpSessionTest, TestBuffersTooSmall) {
+  int out_len;
+  EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  EXPECT_FALSE(s1_.ProtectRtp(rtp_packet_, rtp_len_,
+                              sizeof(rtp_packet_) - 10, &out_len));
+  EXPECT_FALSE(s1_.ProtectRtcp(rtcp_packet_, rtcp_len_,
+                               sizeof(rtcp_packet_) - 14, &out_len));
+}
+
+TEST_F(SrtpSessionTest, TestReplay) {
+  static const uint16 kMaxSeqnum = static_cast<uint16>(-1);
+  static const uint16 seqnum_big = 62275;
+  static const uint16 seqnum_small = 10;
+  static const uint16 replay_window = 1024;
+  int out_len;
+
+  EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+  EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+
+  // Initial sequence number.
+  talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2, seqnum_big);
+  EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+                             &out_len));
+
+  // Replay within the 1024 window should succeed.
+  talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2,
+                     seqnum_big - replay_window + 1);
+  EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+                             &out_len));
+
+  // Replay out side of the 1024 window should fail.
+  talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2,
+                     seqnum_big - replay_window - 1);
+  EXPECT_FALSE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+                              &out_len));
+
+  // Increment sequence number to a small number.
+  talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2, seqnum_small);
+  EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+                             &out_len));
+
+  // Replay around 0 but out side of the 1024 window should fail.
+  talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2,
+                     kMaxSeqnum + seqnum_small - replay_window - 1);
+  EXPECT_FALSE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+                              &out_len));
+
+  // Replay around 0 but within the 1024 window should succeed.
+  for (uint16 seqnum = 65000; seqnum < 65003; ++seqnum) {
+    talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2, seqnum);
+    EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+                               &out_len));
+  }
+
+  // Go back to normal sequence nubmer.
+  // NOTE: without the fix in libsrtp, this would fail. This is because
+  // without the fix, the loop above would keep incrementing local sequence
+  // number in libsrtp, eventually the new sequence number would go out side
+  // of the window.
+  talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2,
+                     seqnum_small + 1);
+  EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+                             &out_len));
+}
+
+class SrtpStatTest
+    : public testing::Test,
+      public sigslot::has_slots<> {
+ public:
+  SrtpStatTest()
+      : ssrc_(0U),
+        mode_(-1),
+        error_(cricket::SrtpFilter::ERROR_NONE) {
+    srtp_stat_.SignalSrtpError.connect(this, &SrtpStatTest::OnSrtpError);
+    srtp_stat_.set_signal_silent_time(200);
+  }
+
+ protected:
+  void OnSrtpError(uint32 ssrc, cricket::SrtpFilter::Mode mode,
+                   cricket::SrtpFilter::Error error) {
+    ssrc_ = ssrc;
+    mode_ = mode;
+    error_ = error;
+  }
+  void Reset() {
+    ssrc_ = 0U;
+    mode_ = -1;
+    error_ = cricket::SrtpFilter::ERROR_NONE;
+  }
+
+  cricket::SrtpStat srtp_stat_;
+  uint32 ssrc_;
+  int mode_;
+  cricket::SrtpFilter::Error error_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SrtpStatTest);
+};
+
+TEST_F(SrtpStatTest, TestProtectRtpError) {
+  Reset();
+  srtp_stat_.AddProtectRtpResult(1, err_status_ok);
+  EXPECT_EQ(0U, ssrc_);
+  EXPECT_EQ(-1, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+  Reset();
+  srtp_stat_.AddProtectRtpResult(1, err_status_auth_fail);
+  EXPECT_EQ(1U, ssrc_);
+  EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_AUTH, error_);
+  Reset();
+  srtp_stat_.AddProtectRtpResult(1, err_status_fail);
+  EXPECT_EQ(1U, ssrc_);
+  EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+  // Within 200ms, the error will not be triggered.
+  Reset();
+  srtp_stat_.AddProtectRtpResult(1, err_status_fail);
+  EXPECT_EQ(0U, ssrc_);
+  EXPECT_EQ(-1, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+  // Now the error will be triggered again.
+  Reset();
+  talk_base::Thread::Current()->SleepMs(210);
+  srtp_stat_.AddProtectRtpResult(1, err_status_fail);
+  EXPECT_EQ(1U, ssrc_);
+  EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+}
+
+TEST_F(SrtpStatTest, TestUnprotectRtpError) {
+  Reset();
+  srtp_stat_.AddUnprotectRtpResult(1, err_status_ok);
+  EXPECT_EQ(0U, ssrc_);
+  EXPECT_EQ(-1, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+  Reset();
+  srtp_stat_.AddUnprotectRtpResult(1, err_status_auth_fail);
+  EXPECT_EQ(1U, ssrc_);
+  EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_AUTH, error_);
+  Reset();
+  srtp_stat_.AddUnprotectRtpResult(1, err_status_replay_fail);
+  EXPECT_EQ(1U, ssrc_);
+  EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_REPLAY, error_);
+  Reset();
+  talk_base::Thread::Current()->SleepMs(210);
+  srtp_stat_.AddUnprotectRtpResult(1, err_status_replay_old);
+  EXPECT_EQ(1U, ssrc_);
+  EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_REPLAY, error_);
+  Reset();
+  srtp_stat_.AddUnprotectRtpResult(1, err_status_fail);
+  EXPECT_EQ(1U, ssrc_);
+  EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+  // Within 200ms, the error will not be triggered.
+  Reset();
+  srtp_stat_.AddUnprotectRtpResult(1, err_status_fail);
+  EXPECT_EQ(0U, ssrc_);
+  EXPECT_EQ(-1, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+  // Now the error will be triggered again.
+  Reset();
+  talk_base::Thread::Current()->SleepMs(210);
+  srtp_stat_.AddUnprotectRtpResult(1, err_status_fail);
+  EXPECT_EQ(1U, ssrc_);
+  EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+}
+
+TEST_F(SrtpStatTest, TestProtectRtcpError) {
+  Reset();
+  srtp_stat_.AddProtectRtcpResult(err_status_ok);
+  EXPECT_EQ(-1, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+  Reset();
+  srtp_stat_.AddProtectRtcpResult(err_status_auth_fail);
+  EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_AUTH, error_);
+  Reset();
+  srtp_stat_.AddProtectRtcpResult(err_status_fail);
+  EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+  // Within 200ms, the error will not be triggered.
+  Reset();
+  srtp_stat_.AddProtectRtcpResult(err_status_fail);
+  EXPECT_EQ(-1, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+  // Now the error will be triggered again.
+  Reset();
+  talk_base::Thread::Current()->SleepMs(210);
+  srtp_stat_.AddProtectRtcpResult(err_status_fail);
+  EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+}
+
+TEST_F(SrtpStatTest, TestUnprotectRtcpError) {
+  Reset();
+  srtp_stat_.AddUnprotectRtcpResult(err_status_ok);
+  EXPECT_EQ(-1, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+  Reset();
+  srtp_stat_.AddUnprotectRtcpResult(err_status_auth_fail);
+  EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_AUTH, error_);
+  Reset();
+  srtp_stat_.AddUnprotectRtcpResult(err_status_replay_fail);
+  EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_REPLAY, error_);
+  Reset();
+  talk_base::Thread::Current()->SleepMs(210);
+  srtp_stat_.AddUnprotectRtcpResult(err_status_replay_fail);
+  EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_REPLAY, error_);
+  Reset();
+  srtp_stat_.AddUnprotectRtcpResult(err_status_fail);
+  EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+  // Within 200ms, the error will not be triggered.
+  Reset();
+  srtp_stat_.AddUnprotectRtcpResult(err_status_fail);
+  EXPECT_EQ(-1, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+  // Now the error will be triggered again.
+  Reset();
+  talk_base::Thread::Current()->SleepMs(210);
+  srtp_stat_.AddUnprotectRtcpResult(err_status_fail);
+  EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+  EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+}
diff --git a/talk/session/phone/ssrcmuxfilter.cc b/talk/session/phone/ssrcmuxfilter.cc
new file mode 100644
index 0000000..d81feab
--- /dev/null
+++ b/talk/session/phone/ssrcmuxfilter.cc
@@ -0,0 +1,90 @@
+/*
+ * 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/session/phone/ssrcmuxfilter.h"
+
+#include <algorithm>
+
+#include "talk/base/logging.h"
+#include "talk/session/phone/rtputils.h"
+
+namespace cricket {
+
+static const uint32 kSsrc01 = 0x01;
+
+SsrcMuxFilter::SsrcMuxFilter() {
+}
+
+SsrcMuxFilter::~SsrcMuxFilter() {
+}
+
+bool SsrcMuxFilter::IsActive() const {
+  return !streams_.empty();
+}
+
+bool SsrcMuxFilter::DemuxPacket(const char* data, size_t len, bool rtcp) {
+  uint32 ssrc = 0;
+  if (!rtcp) {
+    GetRtpSsrc(data, len, &ssrc);
+  } else {
+    int pl_type = 0;
+    if (!GetRtcpType(data, len, &pl_type)) return false;
+    if (pl_type == kRtcpTypeSDES) {
+      // SDES packet parsing not supported.
+      LOG(LS_INFO) << "SDES packet received for demux.";
+      return true;
+    } else {
+      if (!GetRtcpSsrc(data, len, &ssrc)) return false;
+      if (ssrc == kSsrc01) {
+        // SSRC 1 has a special meaning and indicates generic feedback on
+        // some systems and should never be dropped.  If it is forwarded
+        // incorrectly it will be ignored by lower layers anyway.
+        return true;
+      }
+    }
+  }
+  return FindStream(ssrc);
+}
+
+bool SsrcMuxFilter::AddStream(const StreamParams& stream) {
+  if (GetStreamBySsrc(streams_, stream.first_ssrc(), NULL)) {
+      LOG(LS_WARNING) << "Stream already added to filter";
+      return false;
+  }
+  streams_.push_back(stream);
+  return true;
+}
+
+bool SsrcMuxFilter::RemoveStream(uint32 ssrc) {
+  return RemoveStreamBySsrc(&streams_, ssrc);
+}
+
+bool SsrcMuxFilter::FindStream(uint32 ssrc) const {
+  return (GetStreamBySsrc(streams_, ssrc, NULL));
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/ssrcmuxfilter.h b/talk/session/phone/ssrcmuxfilter.h
new file mode 100644
index 0000000..2f28799
--- /dev/null
+++ b/talk/session/phone/ssrcmuxfilter.h
@@ -0,0 +1,67 @@
+/*
+ * 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_SESSION_PHONE_SSRCMUXFILTER_H_
+#define TALK_SESSION_PHONE_SSRCMUXFILTER_H_
+
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/session/phone/streamparams.h"
+
+namespace cricket {
+
+// This class maintains list of recv SSRC's destined for cricket::BaseChannel.
+// In case of single RTP session and single transport channel, all session
+// ( or media) channels share a common transport channel. Hence they all get
+// SignalReadPacket when packet received on transport channel. This requires
+// cricket::BaseChannel to know all the valid sources, else media channel
+// will decode invalid packets.
+class SsrcMuxFilter {
+ public:
+  SsrcMuxFilter();
+  ~SsrcMuxFilter();
+
+  // Whether the rtp mux is active for a sdp session.
+  // Returns true if the filter contains a stream.
+  bool IsActive() const;
+  // Determines packet belongs to valid cricket::BaseChannel.
+  bool DemuxPacket(const char* data, size_t len, bool rtcp);
+  // Adding a valid source to the filter.
+  bool AddStream(const StreamParams& stream);
+  // Removes source from the filter.
+  bool RemoveStream(uint32 ssrc);
+  // Utility method added for unitest.
+  bool FindStream(uint32 ssrc) const;
+
+ private:
+  std::vector<StreamParams> streams_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_SSRCMUXFILTER_H_
diff --git a/talk/session/phone/ssrcmuxfilter_unittest.cc b/talk/session/phone/ssrcmuxfilter_unittest.cc
new file mode 100644
index 0000000..14f4f73
--- /dev/null
+++ b/talk/session/phone/ssrcmuxfilter_unittest.cc
@@ -0,0 +1,184 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/ssrcmuxfilter.h"
+
+static const int kSsrc1 = 0x1111;
+static const int kSsrc2 = 0x2222;
+static const int kSsrc3 = 0x3333;
+
+using cricket::StreamParams;
+
+// SSRC = 0x1111
+static const unsigned char kRtpPacketSsrc1[] = {
+    0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11,
+};
+
+// SSRC = 0x2222
+static const unsigned char kRtpPacketSsrc2[] = {
+    0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22,
+};
+
+// SSRC = 0
+static const unsigned char kRtpPacketInvalidSsrc[] = {
+    0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+// invalid size
+static const unsigned char kRtpPacketTooSmall[] = {
+    0x80, 0x80, 0x00, 0x00,
+};
+
+// PT = 200 = SR, len = 28, SSRC of sender = 0x0001
+// NTP TS = 0, RTP TS = 0, packet count = 0
+static const unsigned char kRtcpPacketSrSsrc01[] = {
+    0x80, 0xC8, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+};
+
+// PT = 200 = SR, len = 28, SSRC of sender = 0x2222
+// NTP TS = 0, RTP TS = 0, packet count = 0
+static const unsigned char kRtcpPacketSrSsrc2[] = {
+    0x80, 0xC8, 0x00, 0x1B, 0x00, 0x00, 0x22, 0x22,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+};
+
+// First packet - SR = PT = 200, len = 0, SSRC of sender = 0x1111
+// NTP TS = 0, RTP TS = 0, packet count = 0
+// second packet - SDES = PT =  202, count = 0, SSRC = 0x1111, cname len = 0
+static const unsigned char kRtcpPacketCompoundSrSdesSsrc1[] = {
+    0x80, 0xC8, 0x00, 0x01, 0x00, 0x00, 0x11, 0x11,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x81, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x01, 0x00,
+};
+
+// SDES = PT =  202, count = 0, SSRC = 0x2222, cname len = 0
+static const unsigned char kRtcpPacketSdesSsrc2[] = {
+    0x81, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x01, 0x00,
+};
+
+// Packet has only mandatory fixed RTCP header
+static const unsigned char kRtcpPacketFixedHeaderOnly[] = {
+    0x80, 0xC8, 0x00, 0x00,
+};
+
+// Small packet for SSRC demux.
+static const unsigned char kRtcpPacketTooSmall[] = {
+    0x80, 0xC8, 0x00, 0x00, 0x00, 0x00,
+};
+
+// PT = 206, FMT = 1, Sender SSRC  = 0x1111, Media SSRC = 0x1111
+// No FCI information is needed for PLI.
+static const unsigned char kRtcpPacketNonCompoundRtcpPliFeedback[] = {
+    0x81, 0xCE, 0x00, 0x0C, 0x00, 0x00, 0x11, 0x11, 0x00, 0x00, 0x11, 0x11,
+};
+
+TEST(SsrcMuxFilterTest, AddRemoveStreamTest) {
+  cricket::SsrcMuxFilter ssrc_filter;
+  EXPECT_FALSE(ssrc_filter.IsActive());
+  EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc1)));
+  StreamParams stream2;
+  stream2.ssrcs.push_back(kSsrc2);
+  stream2.ssrcs.push_back(kSsrc3);
+  EXPECT_TRUE(ssrc_filter.AddStream(stream2));
+
+  EXPECT_TRUE(ssrc_filter.IsActive());
+  EXPECT_TRUE(ssrc_filter.FindStream(kSsrc1));
+  EXPECT_TRUE(ssrc_filter.FindStream(kSsrc2));
+  EXPECT_TRUE(ssrc_filter.FindStream(kSsrc3));
+  EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc1));
+  EXPECT_FALSE(ssrc_filter.FindStream(kSsrc1));
+  EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc3));
+  EXPECT_FALSE(ssrc_filter.RemoveStream(kSsrc2));  // Already removed.
+  EXPECT_FALSE(ssrc_filter.IsActive());
+}
+
+TEST(SsrcMuxFilterTest, RtpPacketTest) {
+  cricket::SsrcMuxFilter ssrc_filter;
+  EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc1)));
+  EXPECT_TRUE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtpPacketSsrc1),
+      sizeof(kRtpPacketSsrc1), false));
+  EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc2)));
+  EXPECT_TRUE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtpPacketSsrc2),
+      sizeof(kRtpPacketSsrc2), false));
+  EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc2));
+  EXPECT_FALSE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtpPacketSsrc2),
+      sizeof(kRtpPacketSsrc2), false));
+  EXPECT_FALSE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtpPacketInvalidSsrc),
+      sizeof(kRtpPacketInvalidSsrc), false));
+  EXPECT_FALSE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtpPacketTooSmall),
+      sizeof(kRtpPacketTooSmall), false));
+}
+
+TEST(SsrcMuxFilterTest, RtcpPacketTest) {
+  cricket::SsrcMuxFilter ssrc_filter;
+  EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc1)));
+  EXPECT_TRUE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtcpPacketCompoundSrSdesSsrc1),
+      sizeof(kRtcpPacketCompoundSrSdesSsrc1), true));
+  EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc2)));
+  EXPECT_TRUE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtcpPacketSrSsrc2),
+      sizeof(kRtcpPacketSrSsrc2), true));
+  EXPECT_TRUE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtcpPacketSdesSsrc2),
+      sizeof(kRtcpPacketSdesSsrc2), true));
+  EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc2));
+  // RTCP Packets other than SR and RR are demuxed regardless of SSRC.
+  EXPECT_TRUE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtcpPacketSdesSsrc2),
+      sizeof(kRtcpPacketSdesSsrc2), true));
+  // RTCP Packets with 'special' SSRC 0x01 are demuxed also
+  EXPECT_TRUE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtcpPacketSrSsrc01),
+      sizeof(kRtcpPacketSrSsrc01), true));
+  EXPECT_FALSE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtcpPacketSrSsrc2),
+      sizeof(kRtcpPacketSrSsrc2), true));
+  EXPECT_FALSE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtcpPacketFixedHeaderOnly),
+      sizeof(kRtcpPacketFixedHeaderOnly), true));
+  EXPECT_FALSE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtcpPacketTooSmall),
+      sizeof(kRtcpPacketTooSmall), true));
+  EXPECT_TRUE(ssrc_filter.DemuxPacket(
+      reinterpret_cast<const char*>(kRtcpPacketNonCompoundRtcpPliFeedback),
+      sizeof(kRtcpPacketNonCompoundRtcpPliFeedback), true));
+}
diff --git a/talk/session/phone/streamparams.cc b/talk/session/phone/streamparams.cc
new file mode 100644
index 0000000..415c4c7
--- /dev/null
+++ b/talk/session/phone/streamparams.cc
@@ -0,0 +1,90 @@
+/*
+ * 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/session/phone/streamparams.h"
+
+namespace cricket {
+
+bool GetStreamBySsrc(const StreamParamsVec& streams, uint32 ssrc,
+                     StreamParams* stream_out) {
+  for (StreamParamsVec::const_iterator stream = streams.begin();
+       stream != streams.end(); ++stream) {
+    if (stream->has_ssrc(ssrc)) {
+      if (stream_out != NULL)
+        *stream_out = *stream;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GetStreamByNickAndName(const StreamParamsVec& streams,
+                            const std::string& nick,
+                            const std::string& name,
+                            StreamParams* stream_out) {
+  for (StreamParamsVec::const_iterator stream = streams.begin();
+       stream != streams.end(); ++stream) {
+    if (stream->nick == nick && stream->name == name) {
+      if (stream_out != NULL)
+        *stream_out = *stream;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool RemoveStreamBySsrc(StreamParamsVec* streams, uint32 ssrc) {
+  bool ret = false;
+  for (StreamParamsVec::iterator stream = streams->begin();
+       stream != streams->end(); ) {
+    if (stream->has_ssrc(ssrc)) {
+      stream = streams->erase(stream);
+      ret = true;
+    } else {
+      ++stream;
+    }
+  }
+  return ret;
+}
+
+bool RemoveStreamByNickAndName(StreamParamsVec* streams,
+                               const std::string& nick,
+                               const std::string& name) {
+  bool ret = false;
+  for (StreamParamsVec::iterator stream = streams->begin();
+       stream != streams->end(); ) {
+    if (stream->nick == nick && stream->name == name) {
+      stream = streams->erase(stream);
+      ret = true;
+    } else {
+      ++stream;
+    }
+  }
+  return ret;
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/streamparams.h b/talk/session/phone/streamparams.h
new file mode 100644
index 0000000..e202f41
--- /dev/null
+++ b/talk/session/phone/streamparams.h
@@ -0,0 +1,148 @@
+/*
+ * 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.
+ */
+
+// This file contains structures for describing SSRCs from a media source such
+// as a MediaStreamTrack when it is sent across an RTP session. Multiple media
+// sources may be sent across the same RTP session, each of them will be
+// described by one StreamParams object
+// SsrcGroup is used to describe the relationship between the SSRCs that
+// are used for this media source.
+// E.x: Consider a source that is sent as 3 simulcast streams
+// Let the simulcast elements have SSRC 10, 20, 30.
+// Let each simulcast element use FEC and let the protection packets have
+// SSRC 11,21,31.
+// To describe this 4 SsrcGroups are needed,
+// StreamParams would then contain ssrc = {10,11,20,21,30,31} and
+// ssrc_groups = {{SIM,{10,20,30}, {FEC,{10,11}, {FEC, {20,21}, {FEC {30,31}}}
+// Please see RFC 5576.
+
+#ifndef TALK_SESSION_PHONE_STREAMPARAMS_H_
+#define TALK_SESSION_PHONE_STREAMPARAMS_H_
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+
+namespace cricket {
+
+struct SsrcGroup {
+  SsrcGroup(const std::string& usage, const std::vector<uint32>& ssrcs)
+      : semantics(usage), ssrcs(ssrcs) {
+  }
+
+  bool operator==(const SsrcGroup& other) const {
+    return (semantics == other.semantics && ssrcs == other.ssrcs);
+  }
+  bool operator!=(const SsrcGroup &other) const {
+    return !(*this == other);
+  }
+
+  std::string semantics;  // e.g FIX, FEC, SIM.
+  std::vector<uint32> ssrcs;  // SSRCs of this type.
+};
+
+struct StreamParams {
+  static StreamParams CreateLegacy(uint32 ssrc) {
+    StreamParams stream;
+    stream.ssrcs.push_back(ssrc);
+    return stream;
+  }
+  bool operator==(const StreamParams& other) const {
+    return (nick == other.nick &&
+            name == other.name &&
+            ssrcs == other.ssrcs &&
+            ssrc_groups == other.ssrc_groups &&
+            type == other.type &&
+            display == other.display &&
+            cname == other.cname &&
+            sync_label == sync_label);
+  }
+  bool operator!=(const StreamParams &other) const {
+    return !(*this == other);
+  }
+
+  uint32 first_ssrc() const {
+    if (ssrcs.empty()) {
+      return 0;
+    }
+
+    return ssrcs[0];
+  }
+  bool has_ssrcs() const {
+    return !ssrcs.empty();
+  }
+  bool has_ssrc(uint32 ssrc) const {
+    return std::find(ssrcs.begin(), ssrcs.end(), ssrc) != ssrcs.end();
+  }
+  void add_ssrc(uint32 ssrc) {
+    ssrcs.push_back(ssrc);
+  }
+
+  // Resource of the MUC jid of the participant of with this stream.
+  // For 1:1 calls, should be left empty (which means remote streams
+  // and local streams should not be mixed together).
+  std::string nick;
+  // Unique name of this source (unique per-nick, not for all nicks)
+  std::string name;
+  std::vector<uint32> ssrcs;  // All SSRCs for this source
+  std::vector<SsrcGroup> ssrc_groups;  // e.g. FID, FEC, SIM
+  // Examples: "camera", "screencast"
+  std::string type;
+  // Friendly name describing stream
+  std::string display;
+  std::string cname;  // RTCP CNAME
+  std::string sync_label;  // Friendly name of cname.
+};
+
+typedef std::vector<StreamParams> StreamParamsVec;
+
+// Finds the stream in streams with the specified ssrc.
+// If you are only interested in the stream exist it is ok to call this function
+// stream_out = NULL.
+bool GetStreamBySsrc(const StreamParamsVec& streams, uint32 ssrc,
+                     StreamParams* stream_out);
+
+// Finds the stream in streams with the specified nick and name.
+// If you are only interested in the stream exist it is ok to call this function
+// stream_out = NULL.
+bool GetStreamByNickAndName(const StreamParamsVec& streams,
+                            const std::string& nick,
+                            const std::string& name,
+                            StreamParams* stream_out);
+
+// Removes the stream with ssrc from streams. Returns true if a stream is
+// removed, false otherwise.
+bool RemoveStreamBySsrc(StreamParamsVec* streams, uint32 ssrc);
+bool RemoveStreamByNickAndName(StreamParamsVec* streams,
+                               const std::string& nick,
+                               const std::string& name);
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_STREAMPARAMS_H_
diff --git a/talk/session/phone/testdata/captured-320x240-2s-48.frames b/talk/session/phone/testdata/captured-320x240-2s-48.frames
new file mode 100644
index 0000000..292a170
--- /dev/null
+++ b/talk/session/phone/testdata/captured-320x240-2s-48.frames
Binary files differ
diff --git a/talk/session/phone/testdata/h264-svc-99-640x360.rtpdump b/talk/session/phone/testdata/h264-svc-99-640x360.rtpdump
new file mode 100644
index 0000000..ffa521d
--- /dev/null
+++ b/talk/session/phone/testdata/h264-svc-99-640x360.rtpdump
Binary files differ
diff --git a/talk/session/phone/testutils.cc b/talk/session/phone/testutils.cc
new file mode 100644
index 0000000..f427f96
--- /dev/null
+++ b/talk/session/phone/testutils.cc
@@ -0,0 +1,370 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/testutils.h"
+
+#include <math.h>
+
+#ifdef HAVE_YUV
+#include "libyuv/compare.h"
+#endif
+#include "talk/base/bytebuffer.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/rtpdump.h"
+#include "talk/session/phone/videocapturer.h"
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+
+/////////////////////////////////////////////////////////////////////////
+// Implementation of RawRtpPacket
+/////////////////////////////////////////////////////////////////////////
+void RawRtpPacket::WriteToByteBuffer(
+    uint32 in_ssrc, talk_base::ByteBuffer *buf) const {
+  if (!buf) return;
+
+  buf->WriteUInt8(ver_to_cc);
+  buf->WriteUInt8(m_to_pt);
+  buf->WriteUInt16(sequence_number);
+  buf->WriteUInt32(timestamp);
+  buf->WriteUInt32(in_ssrc);
+  buf->WriteBytes(payload, sizeof(payload));
+}
+
+bool RawRtpPacket::ReadFromByteBuffer(talk_base::ByteBuffer* buf) {
+  if (!buf) return false;
+
+  bool ret = true;
+  ret &= buf->ReadUInt8(&ver_to_cc);
+  ret &= buf->ReadUInt8(&m_to_pt);
+  ret &= buf->ReadUInt16(&sequence_number);
+  ret &= buf->ReadUInt32(&timestamp);
+  ret &= buf->ReadUInt32(&ssrc);
+  ret &= buf->ReadBytes(payload, sizeof(payload));
+  return ret;
+}
+
+bool RawRtpPacket::SameExceptSeqNumTimestampSsrc(
+    const RawRtpPacket& packet, uint16 seq, uint32 ts, uint32 ssc) const {
+  return sequence_number == seq &&
+      timestamp == ts &&
+      ver_to_cc == packet.ver_to_cc &&
+      m_to_pt == packet.m_to_pt &&
+      ssrc == ssc &&
+      0 == memcmp(payload, packet.payload, sizeof(payload));
+}
+
+/////////////////////////////////////////////////////////////////////////
+// Implementation of RawRtcpPacket
+/////////////////////////////////////////////////////////////////////////
+void RawRtcpPacket::WriteToByteBuffer(talk_base::ByteBuffer *buf) const {
+  if (!buf) return;
+
+  buf->WriteUInt8(ver_to_count);
+  buf->WriteUInt8(type);
+  buf->WriteUInt16(length);
+  buf->WriteBytes(payload, sizeof(payload));
+}
+
+bool RawRtcpPacket::ReadFromByteBuffer(talk_base::ByteBuffer* buf) {
+  if (!buf) return false;
+
+  bool ret = true;
+  ret &= buf->ReadUInt8(&ver_to_count);
+  ret &= buf->ReadUInt8(&type);
+  ret &= buf->ReadUInt16(&length);
+  ret &= buf->ReadBytes(payload, sizeof(payload));
+  return ret;
+}
+
+bool RawRtcpPacket::EqualsTo(const RawRtcpPacket& packet) const {
+  return ver_to_count == packet.ver_to_count &&
+      type == packet.type &&
+      length == packet.length &&
+      0 == memcmp(payload, packet.payload, sizeof(payload));
+}
+
+/////////////////////////////////////////////////////////////////////////
+// Implementation of class RtpTestUtility
+/////////////////////////////////////////////////////////////////////////
+const RawRtpPacket RtpTestUtility::kTestRawRtpPackets[] = {
+    {0x80, 0, 0, 0,  RtpTestUtility::kDefaultSsrc, "RTP frame 0"},
+    {0x80, 0, 1, 30, RtpTestUtility::kDefaultSsrc, "RTP frame 1"},
+    {0x80, 0, 2, 30, RtpTestUtility::kDefaultSsrc, "RTP frame 1"},
+    {0x80, 0, 3, 60, RtpTestUtility::kDefaultSsrc, "RTP frame 2"}
+};
+const RawRtcpPacket RtpTestUtility::kTestRawRtcpPackets[] = {
+    // The Version is 2, the Length is 2, and the payload has 8 bytes.
+    {0x80, 0, 2, "RTCP0000"},
+    {0x80, 0, 2, "RTCP0001"},
+    {0x80, 0, 2, "RTCP0002"},
+    {0x80, 0, 2, "RTCP0003"},
+};
+
+size_t RtpTestUtility::GetTestPacketCount() {
+  return talk_base::_min(
+      ARRAY_SIZE(kTestRawRtpPackets),
+      ARRAY_SIZE(kTestRawRtcpPackets));
+}
+
+bool RtpTestUtility::WriteTestPackets(
+    size_t count, bool rtcp, uint32 rtp_ssrc, RtpDumpWriter* writer) {
+  if (!writer || count > GetTestPacketCount()) return false;
+
+  bool result = true;
+  uint32 elapsed_time_ms = 0;
+  for (size_t i = 0; i < count && result; ++i) {
+    talk_base::ByteBuffer buf;
+    if (rtcp) {
+      kTestRawRtcpPackets[i].WriteToByteBuffer(&buf);
+    } else {
+      kTestRawRtpPackets[i].WriteToByteBuffer(rtp_ssrc, &buf);
+    }
+
+    RtpDumpPacket dump_packet(buf.Data(), buf.Length(), elapsed_time_ms, rtcp);
+    elapsed_time_ms += kElapsedTimeInterval;
+    result &= (talk_base::SR_SUCCESS == writer->WritePacket(dump_packet));
+  }
+  return result;
+}
+
+bool RtpTestUtility::VerifyTestPacketsFromStream(
+    size_t count, talk_base::StreamInterface* stream, uint32 ssrc) {
+  if (!stream) return false;
+
+  uint32 prev_elapsed_time = 0;
+  bool result = true;
+  stream->Rewind();
+  RtpDumpLoopReader reader(stream);
+  for (size_t i = 0; i < count && result; ++i) {
+    // Which loop and which index in the loop are we reading now.
+    size_t loop = i / GetTestPacketCount();
+    size_t index = i % GetTestPacketCount();
+
+    RtpDumpPacket packet;
+    result &= (talk_base::SR_SUCCESS == reader.ReadPacket(&packet));
+    // Check the elapsed time of the dump packet.
+    result &= (packet.elapsed_time >= prev_elapsed_time);
+    prev_elapsed_time = packet.elapsed_time;
+
+    // Check the RTP or RTCP packet.
+    talk_base::ByteBuffer buf(reinterpret_cast<const char*>(&packet.data[0]),
+                              packet.data.size());
+    if (packet.is_rtcp) {
+      // RTCP packet.
+      RawRtcpPacket rtcp_packet;
+      result &= rtcp_packet.ReadFromByteBuffer(&buf);
+      result &= rtcp_packet.EqualsTo(kTestRawRtcpPackets[index]);
+    } else {
+      // RTP packet.
+      RawRtpPacket rtp_packet;
+      result &= rtp_packet.ReadFromByteBuffer(&buf);
+      result &= rtp_packet.SameExceptSeqNumTimestampSsrc(
+          kTestRawRtpPackets[index],
+          kTestRawRtpPackets[index].sequence_number +
+              loop * GetTestPacketCount(),
+          kTestRawRtpPackets[index].timestamp + loop * kRtpTimestampIncrease,
+          ssrc);
+    }
+  }
+
+  stream->Rewind();
+  return result;
+}
+
+bool RtpTestUtility::VerifyPacket(const RtpDumpPacket* dump,
+                                  const RawRtpPacket* raw,
+                                  bool header_only) {
+  if (!dump || !raw) return false;
+
+  talk_base::ByteBuffer buf;
+  raw->WriteToByteBuffer(RtpTestUtility::kDefaultSsrc, &buf);
+
+  if (header_only) {
+    size_t header_len = 0;
+    dump->GetRtpHeaderLen(&header_len);
+    return header_len == dump->data.size() &&
+        buf.Length() > dump->data.size() &&
+        0 == memcmp(buf.Data(), &dump->data[0], dump->data.size());
+  } else {
+    return buf.Length() == dump->data.size() &&
+        0 == memcmp(buf.Data(), &dump->data[0], dump->data.size());
+  }
+}
+
+// Implementation of VideoCaptureListener.
+VideoCapturerListener::VideoCapturerListener(VideoCapturer* capturer)
+    : start_result_(CR_PENDING),
+      frame_count_(0),
+      frame_fourcc_(0),
+      frame_width_(0),
+      frame_height_(0),
+      frame_size_(0),
+      resolution_changed_(false) {
+  capturer->SignalStartResult.connect(this,
+      &VideoCapturerListener::OnStartResult);
+  capturer->SignalFrameCaptured.connect(this,
+      &VideoCapturerListener::OnFrameCaptured);
+  capturer->SignalCaptureEvent.connect(this,
+      &VideoCapturerListener::OnCaptureEvent);
+}
+
+void VideoCapturerListener::OnStartResult(VideoCapturer* capturer,
+                                          CaptureResult result) {
+  start_result_ = result;
+}
+
+void VideoCapturerListener::OnFrameCaptured(VideoCapturer* capturer,
+                                            const CapturedFrame* frame) {
+  ++frame_count_;
+  if (1 == frame_count_) {
+    frame_fourcc_ = frame->fourcc;
+    frame_width_ = frame->width;
+    frame_height_ = frame->height;
+    frame_size_ = frame->data_size;
+  } else if (frame_width_ != frame->width || frame_height_ != frame->height) {
+    resolution_changed_ = true;
+  }
+}
+
+void VideoCapturerListener::OnCaptureEvent(VideoCapturer* capturer,
+                                           CaptureEvent ev) {
+}
+
+
+// Returns the absolute path to a file in the testdata/ directory.
+std::string GetTestFilePath(const std::string& filename) {
+  // Locate test data directory.
+  talk_base::Pathname path = GetTalkDirectory();
+  EXPECT_FALSE(path.empty());  // must be run from inside "talk"
+  path.AppendFolder("session");
+  path.AppendFolder("phone");
+  path.AppendFolder("testdata");
+  path.SetFilename(filename);
+  return path.pathname();
+}
+
+// PSNR formula: psnr = 10 * log10 (Peak Signal^2 / mse)
+// sse is set to a small number for identical frames or sse == 0
+double ComputePSNR(double sse, double size) {
+  if (sse <= 0.)
+    sse = 65025.0 * size / pow(10., 128./10.);  // produces max PSNR of 128
+  return 10.0 * log10(65025.0 * size / sse);
+}
+
+double ComputeSumSquareError(const uint8 *org, const uint8 *rec, int size) {
+#ifdef HAVE_YUV
+  return static_cast<double>(libyuv::ComputeSumSquareError(org, rec, size));
+#else
+  double sse = 0.;
+  for (int j = 0; j < size; ++j) {
+    const int diff = static_cast<int>(org[j]) - static_cast<int>(rec[j]);
+    sse += static_cast<double>(diff * diff);
+  }
+  return sse;
+#endif
+}
+
+// Loads the image with the specified prefix and size into |out|.
+bool LoadPlanarYuvTestImage(const std::string& prefix,
+                            int width, int height, uint8* out) {
+  std::stringstream ss;
+  ss << prefix << "." << width << "x" << height << "_P420.yuv";
+
+  talk_base::scoped_ptr<talk_base::FileStream> stream(
+      talk_base::Filesystem::OpenFile(talk_base::Pathname(
+          GetTestFilePath(ss.str())), "rb"));
+  if (!stream.get()) {
+    return false;
+  }
+
+  talk_base::StreamResult res =
+      stream->ReadAll(out, I420_SIZE(width, height), NULL, NULL);
+  return (res == talk_base::SR_SUCCESS);
+}
+
+// Dumps the YUV image out to a file, for visual inspection.
+// PYUV tool can be used to view dump files.
+void DumpPlanarYuvTestImage(const std::string& prefix, const uint8* img,
+                            int w, int h) {
+  talk_base::FileStream fs;
+  char filename[256];
+  talk_base::sprintfn(filename, sizeof(filename), "%s.%dx%d_P420.yuv",
+                      prefix.c_str(), w, h);
+  fs.Open(filename, "wb", NULL);
+  fs.Write(img, I420_SIZE(w, h), NULL, NULL);
+}
+
+// Dumps the ARGB image out to a file, for visual inspection.
+// ffplay tool can be used to view dump files.
+void DumpPlanarArgbTestImage(const std::string& prefix, const uint8* img,
+                             int w, int h) {
+  talk_base::FileStream fs;
+  char filename[256];
+  talk_base::sprintfn(filename, sizeof(filename), "%s.%dx%d_ARGB.raw",
+                      prefix.c_str(), w, h);
+  fs.Open(filename, "wb", NULL);
+  fs.Write(img, ARGB_SIZE(w, h), NULL, NULL);
+}
+
+bool VideoFrameEqual(const VideoFrame* frame0, const VideoFrame* frame1) {
+  const uint8* y0 = frame0->GetYPlane();
+  const uint8* u0 = frame0->GetUPlane();
+  const uint8* v0 = frame0->GetVPlane();
+  const uint8* y1 = frame1->GetYPlane();
+  const uint8* u1 = frame1->GetUPlane();
+  const uint8* v1 = frame1->GetVPlane();
+
+  for (size_t i = 0; i < frame0->GetHeight(); ++i) {
+    if (0 != memcmp(y0, y1, frame0->GetWidth())) {
+      return false;
+    }
+    y0 += frame0->GetYPitch();
+    y1 += frame1->GetYPitch();
+  }
+
+  for (size_t i = 0; i < frame0->GetChromaHeight(); ++i) {
+    if (0 != memcmp(u0, u1, frame0->GetChromaWidth())) {
+      return false;
+    }
+    if (0 != memcmp(v0, v1, frame0->GetChromaWidth())) {
+      return false;
+    }
+    u0 += frame0->GetUPitch();
+    v0 += frame0->GetVPitch();
+    u1 += frame1->GetUPitch();
+    v1 += frame1->GetVPitch();
+  }
+
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/testutils.h b/talk/session/phone/testutils.h
new file mode 100644
index 0000000..f5556e7
--- /dev/null
+++ b/talk/session/phone/testutils.h
@@ -0,0 +1,209 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_TESTUTILS_H_
+#define TALK_SESSION_PHONE_TESTUTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/window.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/videocapturer.h"
+
+namespace talk_base {
+class ByteBuffer;
+class StreamInterface;
+}
+
+namespace cricket {
+
+// Returns size of 420 image with rounding on chroma for odd sizes.
+#define I420_SIZE(w, h) (w * h + (((w + 1) / 2) * ((h + 1) / 2)) * 2)
+// Returns size of ARGB image.
+#define ARGB_SIZE(w, h) (w * h * 4)
+
+template <class T> inline std::vector<T> MakeVector(const T a[], size_t s) {
+  return std::vector<T>(a, a + s);
+}
+#define MAKE_VECTOR(a) cricket::MakeVector(a, ARRAY_SIZE(a))
+
+struct RtpDumpPacket;
+class RtpDumpWriter;
+class VideoFrame;
+
+struct RawRtpPacket {
+  void WriteToByteBuffer(uint32 in_ssrc, talk_base::ByteBuffer* buf) const;
+  bool ReadFromByteBuffer(talk_base::ByteBuffer* buf);
+  // Check if this packet is the same as the specified packet except the
+  // sequence number and timestamp, which should be the same as the specified
+  // parameters.
+  bool SameExceptSeqNumTimestampSsrc(
+      const RawRtpPacket& packet, uint16 seq, uint32 ts, uint32 ssc) const;
+  int size() const { return 28; }
+
+  uint8 ver_to_cc;
+  uint8 m_to_pt;
+  uint16 sequence_number;
+  uint32 timestamp;
+  uint32 ssrc;
+  char payload[16];
+};
+
+struct RawRtcpPacket {
+  void WriteToByteBuffer(talk_base::ByteBuffer* buf) const;
+  bool ReadFromByteBuffer(talk_base::ByteBuffer* buf);
+  bool EqualsTo(const RawRtcpPacket& packet) const;
+
+  uint8 ver_to_count;
+  uint8 type;
+  uint16 length;
+  char payload[16];
+};
+
+class RtpTestUtility {
+ public:
+  static size_t GetTestPacketCount();
+
+  // Write the first count number of kTestRawRtcpPackets or kTestRawRtpPackets,
+  // depending on the flag rtcp. If it is RTP, use the specified SSRC. Return
+  // true if successful.
+  static bool WriteTestPackets(
+      size_t count, bool rtcp, uint32 rtp_ssrc, RtpDumpWriter* writer);
+
+  // Loop read the first count number of packets from the specified stream.
+  // Verify the elapsed time of the dump packets increase monotonically. If the
+  // stream is a RTP stream, verify the RTP sequence number, timestamp, and
+  // payload. If the stream is a RTCP stream, verify the RTCP header and
+  // payload.
+  static bool VerifyTestPacketsFromStream(
+      size_t count, talk_base::StreamInterface* stream, uint32 ssrc);
+
+  // Verify the dump packet is the same as the raw RTP packet.
+  static bool VerifyPacket(const RtpDumpPacket* dump,
+                           const RawRtpPacket* raw,
+                           bool header_only);
+
+  static const uint32 kDefaultSsrc = 1;
+  static const uint32 kRtpTimestampIncrease = 90;
+  static const uint32 kDefaultTimeIncrease = 30;
+  static const uint32 kElapsedTimeInterval = 10;
+  static const RawRtpPacket kTestRawRtpPackets[];
+  static const RawRtcpPacket kTestRawRtcpPackets[];
+
+ private:
+  RtpTestUtility() {}
+};
+
+// Test helper for testing VideoCapturer implementations.
+class VideoCapturerListener : public sigslot::has_slots<> {
+ public:
+  explicit VideoCapturerListener(VideoCapturer* cap);
+
+  CaptureResult start_result() const { return start_result_; }
+  int frame_count() const { return frame_count_; }
+  uint32 frame_fourcc() const { return frame_fourcc_; }
+  int frame_width() const { return frame_width_; }
+  int frame_height() const { return frame_height_; }
+  uint32 frame_size() const { return frame_size_; }
+  bool resolution_changed() const { return resolution_changed_; }
+
+  void OnStartResult(VideoCapturer* capturer, CaptureResult result);
+  void OnFrameCaptured(VideoCapturer* capturer, const CapturedFrame* frame);
+  void OnCaptureEvent(VideoCapturer* capturer, CaptureEvent ev);
+
+ private:
+  CaptureResult start_result_;
+  int frame_count_;
+  uint32 frame_fourcc_;
+  int frame_width_;
+  int frame_height_;
+  uint32 frame_size_;
+  bool resolution_changed_;
+};
+
+class ScreencastEventCatcher : public sigslot::has_slots<> {
+ public:
+  ScreencastEventCatcher() : ssrc_(0), ev_(talk_base::WE_RESIZE) { }
+  uint32 ssrc() const { return ssrc_; }
+  talk_base::WindowEvent event() const { return ev_; }
+  void OnEvent(uint32 ssrc, talk_base::WindowEvent ev) {
+    ssrc_ = ssrc;
+    ev_ = ev;
+  }
+ private:
+  uint32 ssrc_;
+  talk_base::WindowEvent ev_;
+};
+
+class VideoMediaErrorCatcher : public sigslot::has_slots<> {
+ public:
+  VideoMediaErrorCatcher() : ssrc_(0), error_(VideoMediaChannel::ERROR_NONE) { }
+  uint32 ssrc() const { return ssrc_; }
+  VideoMediaChannel::Error error() const { return error_; }
+  void OnError(uint32 ssrc, VideoMediaChannel::Error error) {
+    ssrc_ = ssrc;
+    error_ = error;
+  }
+ private:
+  uint32 ssrc_;
+  VideoMediaChannel::Error error_;
+};
+
+// Returns the absolute path to a file in the testdata/ directory.
+std::string GetTestFilePath(const std::string& filename);
+
+// PSNR formula: psnr = 10 * log10 (Peak Signal^2 / mse)
+// sse is set to a small number for identical frames or sse == 0
+double ComputePSNR(double sse, double size);
+
+double ComputeSumSquareError(const uint8 *org, const uint8 *rec,
+                             int size);
+
+// Loads the image with the specified prefix and size into |out|.
+bool LoadPlanarYuvTestImage(const std::string& prefix,
+                            int width, int height, uint8* out);
+
+// Dumps the YUV image out to a file, for visual inspection.
+// PYUV tool can be used to view dump files.
+void DumpPlanarYuvTestImage(const std::string& prefix, const uint8* img,
+                            int w, int h);
+
+// Dumps the ARGB image out to a file, for visual inspection.
+// ffplay tool can be used to view dump files.
+void DumpPlanarArgbTestImage(const std::string& prefix, const uint8* img,
+                             int w, int h);
+
+// Compare two I420 frames.
+bool VideoFrameEqual(const VideoFrame* frame0, const VideoFrame* frame1);
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_TESTUTILS_H_
diff --git a/talk/session/phone/videoadapter.cc b/talk/session/phone/videoadapter.cc
new file mode 100644
index 0000000..4313fc7
--- /dev/null
+++ b/talk/session/phone/videoadapter.cc
@@ -0,0 +1,497 @@
+// libjingle
+// Copyright 2010 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "talk/session/phone/videoadapter.h"
+
+#include <limits.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/timeutils.h"
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+
+// TODO: Make downgrades settable
+static const int kMaxCpuDowngrades = 2;  // Downgrade at most 2 times for CPU.
+static const int kDefaultDowngradeWaitTimeMs = 2000;
+
+// Cpu system load thresholds relative to max cpus.
+static const float kHighSystemThreshold = 0.95f;
+static const float kLowSystemThreshold = 0.75f;
+
+// Cpu process load thresholds relative to current cpus.
+static const float kMediumProcessThreshold = 0.50f;
+
+// TODO: Consider making scale factor table settable, to allow
+// application to select quality vs performance tradeoff.
+// List of scale factors that adapter will scale by.
+#if defined(IOS) || defined(ANDROID)
+// Mobile needs 1/4 scale for VGA (640x360) to QQVGA (160x90)
+// or 1/4 scale for HVGA (480x270) to QQHVGA (120x67)
+static const int kMinNumPixels = 120 * 67;
+static float kScaleFactors[] = {
+  1.f, // full size
+  3.f/4.f, // 3/4 scale
+  1.f/2.f, // 1/2 scale
+  3.f/8.f, // 3/8 scale
+  1.f/4.f, // 1/4 scale
+};
+#else
+// PC needs 1/8 scale for HD (1280x720) to QQVGA (160x90)
+static const int kMinNumPixels = 160 * 100;
+static float kScaleFactors[] = {
+  1.f, // full size
+  3.f/4.f, // 3/4 scale
+  1.f/2.f, // 1/2 scale
+  3.f/8.f, // 3/8 scale
+  1.f/4.f, // 1/4 scale
+  3.f/16.f, // 3/16 scale
+  1.f/8.f // 1/8 scale
+};
+#endif
+
+// Find scale factor that applied to width and height, is best match
+// to num_pixels.
+float VideoAdapter::FindClosestScale(int width, int height,
+                                     int target_num_pixels) {
+  if (!target_num_pixels) {
+    return 0.f;
+  }
+  int best_distance = INT_MAX;
+  int best_index = 0;  // default to unscaled
+  for (size_t i = 0u; i < ARRAY_SIZE(kScaleFactors); ++i) {
+    int test_num_pixels = static_cast<int>(width * kScaleFactors[i] *
+                                           height * kScaleFactors[i]);
+    int diff = test_num_pixels - target_num_pixels;
+    if (diff < 0) {
+      diff = -diff;
+    }
+    if (diff < best_distance) {
+      best_distance = diff;
+      best_index = i;
+      if (!best_distance) { // Found exact match
+        break;
+      }
+    }
+  }
+  return kScaleFactors[best_index];
+}
+
+// There are several frame sizes used by Adapter.  This explains them
+// input_format - set once by server to frame size expected from the camera.
+// output_format - size that output would like to be.  Includes framerate.
+// output_num_pixels - size that output should be constrained to.  Used to
+//   compute output_format from in_frame.
+// in_frame - actual camera captured frame size, which is typically the same
+//   as input_format.  This can also be rotated or cropped for aspect ratio.
+// out_frame - actual frame output by adapter.  Should be a direct scale of
+//   in_frame maintaining rotation and aspect ratio.
+// OnOutputFormatRequest - server requests you send this resolution based on
+//   view requests.
+// OnEncoderResolutionRequest - encoder requests you send this resolution based
+//   on bandwidth
+// OnCpuLoadUpdated - cpu monitor requests you send this resolution based on
+//   cpu load.
+
+///////////////////////////////////////////////////////////////////////
+// Implementation of VideoAdapter
+VideoAdapter::VideoAdapter()
+    : output_num_pixels_(0),
+      black_output_(false),
+      is_black_(false),
+      drop_frame_count_(0) {
+}
+
+VideoAdapter::~VideoAdapter() {
+}
+
+// TODO: Consider SetInputFormat and SetOutputFormat without
+// VideoFormat.
+void VideoAdapter::SetInputFormat(const VideoFormat& format) {
+  talk_base::CritScope cs(&critical_section_);
+  input_format_ = format;
+  output_format_.interval = talk_base::_max(
+      output_format_.interval, input_format_.interval);
+}
+
+void VideoAdapter::SetOutputFormat(const VideoFormat& format) {
+  talk_base::CritScope cs(&critical_section_);
+  output_format_ = format;
+  output_num_pixels_ = output_format_.width * output_format_.height;
+  output_format_.interval = talk_base::_max(
+      output_format_.interval, input_format_.interval);
+  drop_frame_count_ = 0;
+}
+
+const VideoFormat& VideoAdapter::input_format() {
+  talk_base::CritScope cs(&critical_section_);
+  return input_format_;
+}
+
+const VideoFormat& VideoAdapter::output_format() {
+  talk_base::CritScope cs(&critical_section_);
+  return output_format_;
+}
+
+void VideoAdapter::SetBlackOutput(bool black) {
+  talk_base::CritScope cs(&critical_section_);
+  black_output_ = black;
+}
+
+// Constrain output resolution to this many pixels overall
+void VideoAdapter::SetOutputNumPixels(int num_pixels) {
+  output_num_pixels_ = num_pixels;
+}
+
+int VideoAdapter::GetOutputNumPixels() const {
+  return output_num_pixels_;
+}
+
+bool VideoAdapter::AdaptFrame(const VideoFrame* in_frame,
+                              const VideoFrame** out_frame) {
+  talk_base::CritScope cs(&critical_section_);
+
+  if (!in_frame || !out_frame || input_format_.IsSize0x0()) {
+    return false;
+  }
+
+  // Drop the input frame if necessary.
+  bool should_drop = false;
+  if (!output_num_pixels_) {
+    // Drop all frames as the output format is 0x0.
+    should_drop = true;
+  } else {
+    // Drop some frames based on the ratio of the input fps and the output fps.
+    // We assume that the output fps is a factor of the input fps. In other
+    // words, the output interval is divided by the input interval evenly.
+    should_drop = (drop_frame_count_ > 0);
+    if (input_format_.interval > 0 &&
+        output_format_.interval > input_format_.interval) {
+      ++drop_frame_count_;
+      drop_frame_count_ %= output_format_.interval / input_format_.interval;
+    }
+  }
+
+  if (output_num_pixels_) {
+    float scale = VideoAdapter::FindClosestScale(in_frame->GetWidth(),
+                                                 in_frame->GetHeight(),
+                                                 output_num_pixels_);
+    output_format_.width = static_cast<int>(in_frame->GetWidth() * scale);
+    output_format_.height = static_cast<int>(in_frame->GetHeight() * scale);
+  }
+
+  if (should_drop) {
+    *out_frame = NULL;
+    return true;
+  }
+
+  if (!StretchToOutputFrame(in_frame)) {
+    return false;
+  }
+
+  *out_frame = output_frame_.get();
+  return true;
+}
+
+bool VideoAdapter::StretchToOutputFrame(const VideoFrame* in_frame) {
+  int output_width = output_format_.width;
+  int output_height = output_format_.height;
+
+  // Create and stretch the output frame if it has not been created yet or its
+  // size is not same as the expected.
+  bool stretched = false;
+  if (!output_frame_.get() ||
+      output_frame_->GetWidth() != static_cast<size_t>(output_width) ||
+      output_frame_->GetHeight() != static_cast<size_t>(output_height)) {
+    output_frame_.reset(
+        in_frame->Stretch(output_width, output_height, true, true));
+    if (!output_frame_.get()) {
+      LOG(LS_WARNING) << "Adapter failed to stretch frame to "
+                      << output_width << "x" << output_height;
+      return false;
+    }
+    stretched = true;
+    is_black_ = false;
+  }
+
+  if (!black_output_) {
+    if (!stretched) {
+      // The output frame does not need to be blacken and has not been stretched
+      // from the input frame yet, stretch the input frame. This is the most
+      // common case.
+      in_frame->StretchToFrame(output_frame_.get(), true, true);
+    }
+    is_black_ = false;
+  } else {
+    if (!is_black_) {
+      output_frame_->SetToBlack();
+      is_black_ = true;
+    }
+    output_frame_->SetElapsedTime(in_frame->GetElapsedTime());
+    output_frame_->SetTimeStamp(in_frame->GetTimeStamp());
+  }
+
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////
+// Implementation of CoordinatedVideoAdapter
+CoordinatedVideoAdapter::CoordinatedVideoAdapter()
+    : cpu_adaptation_(false),
+      gd_adaptation_(true),
+      view_adaptation_(true),
+      cpu_downgrade_count_(0),
+      cpu_downgrade_wait_time_(0),
+      view_desired_num_pixels_(INT_MAX),
+      view_desired_interval_(0),
+      encoder_desired_num_pixels_(INT_MAX),
+      cpu_desired_num_pixels_(INT_MAX) {
+}
+
+// Helper function to UPGRADE or DOWNGRADE a number of pixels
+void CoordinatedVideoAdapter::StepPixelCount(
+    CoordinatedVideoAdapter::AdaptRequest request,
+    int* num_pixels) {
+  switch (request) {
+    case CoordinatedVideoAdapter::DOWNGRADE:
+      *num_pixels /= 2;
+      break;
+
+    case CoordinatedVideoAdapter::UPGRADE:
+      *num_pixels *= 2;
+      break;
+
+    default:  // No change in pixel count
+      break;
+  }
+  return;
+}
+
+// Find the adaptation request of the cpu based on the load. Return UPGRADE if
+// the load is low, DOWNGRADE if the load is high, and KEEP otherwise.
+CoordinatedVideoAdapter::AdaptRequest CoordinatedVideoAdapter::FindCpuRequest(
+    int current_cpus, int max_cpus,
+    float process_load, float system_load) {
+  // Downgrade if system is high and plugin is at least more than midrange.
+  if (system_load >= kHighSystemThreshold * max_cpus &&
+      process_load >= kMediumProcessThreshold * current_cpus) {
+    return CoordinatedVideoAdapter::DOWNGRADE;
+  // Upgrade if system is low.
+  } else if (system_load < kLowSystemThreshold * max_cpus) {
+    return CoordinatedVideoAdapter::UPGRADE;
+  }
+  return CoordinatedVideoAdapter::KEEP;
+}
+
+// A remote view request for a new resolution.
+void CoordinatedVideoAdapter::OnOutputFormatRequest(const VideoFormat& format) {
+  talk_base::CritScope cs(&request_critical_section_);
+  if (!view_adaptation_) {
+    return;
+  }
+  // Set output for initial aspect ratio in mediachannel unittests.
+  int old_num_pixels = GetOutputNumPixels();
+  SetOutputFormat(format);
+  SetOutputNumPixels(old_num_pixels);
+  view_desired_num_pixels_ = format.width * format.height;
+  view_desired_interval_ = format.interval;
+  bool changed = AdaptToMinimumFormat();
+  LOG(LS_INFO) << "VAdapt View Request: "
+               << format.width << "x" << format.height
+               << " Pixels: " << view_desired_num_pixels_
+               << " Changed: " << (changed ? "true" : "false");
+}
+
+// A Bandwidth GD request for new resolution
+void CoordinatedVideoAdapter::OnEncoderResolutionRequest(
+    int width, int height, AdaptRequest request) {
+  talk_base::CritScope cs(&request_critical_section_);
+  if (!gd_adaptation_) {
+    return;
+  }
+  if (KEEP != request) {
+    int new_encoder_desired_num_pixels = width * height;
+    int old_num_pixels = GetOutputNumPixels();
+    if (new_encoder_desired_num_pixels != old_num_pixels) {
+      LOG(LS_VERBOSE) << "VAdapt GD resolution stale.  Ignored";
+    } else {
+      // Update the encoder desired format based on the request.
+      encoder_desired_num_pixels_ = new_encoder_desired_num_pixels;
+      StepPixelCount(request, &encoder_desired_num_pixels_);
+    }
+  }
+  bool changed = AdaptToMinimumFormat();
+  LOG(LS_INFO) << "VAdapt GD Request: "
+               << (DOWNGRADE == request ? "down" :
+                   (UPGRADE == request ? "up" : "keep"))
+               << " From: " << width << "x" << height
+               << " Pixels: " << encoder_desired_num_pixels_
+               << " Changed: " << (changed ? "true" : "false");
+}
+
+// A CPU request for new resolution
+void CoordinatedVideoAdapter::OnCpuLoadUpdated(
+    int current_cpus, int max_cpus, float process_load, float system_load) {
+  talk_base::CritScope cs(&request_critical_section_);
+  if (!cpu_adaptation_) {
+    return;
+  }
+  AdaptRequest request = FindCpuRequest(current_cpus, max_cpus,
+                                        process_load, system_load);
+  // Update how many times we have downgraded due to the cpu load.
+  switch (request) {
+    case DOWNGRADE:
+      if (cpu_downgrade_count_ < kMaxCpuDowngrades) {
+        // Ignore downgrades if we have downgraded the maximum times or we just
+        // downgraded in a short time.
+        if (cpu_downgrade_wait_time_ != 0 &&
+            talk_base::TimeIsLater(talk_base::Time(),
+                                   cpu_downgrade_wait_time_)) {
+          LOG(LS_VERBOSE) << "VAdapt CPU load high but do not downgrade until "
+                          << talk_base::TimeUntil(cpu_downgrade_wait_time_)
+                          << " ms.";
+          request = KEEP;
+        } else {
+          ++cpu_downgrade_count_;
+        }
+      } else {
+          LOG(LS_VERBOSE) << "VAdapt CPU load high but do not downgrade "
+                             "because maximum downgrades reached";
+      }
+      break;
+    case UPGRADE:
+      if (cpu_downgrade_count_ > 0) {
+        bool is_min = IsMinimumFormat(cpu_desired_num_pixels_);
+        if (is_min) {
+          --cpu_downgrade_count_;
+        } else {
+         LOG(LS_VERBOSE) << "VAdapt CPU load low but do not upgrade "
+                             "because cpu is not limiting resolution";
+        }
+      } else {
+          LOG(LS_VERBOSE) << "VAdapt CPU load low but do not upgrade "
+                             "because minimum downgrades reached";
+      }
+      break;
+    case KEEP:
+    default:
+      break;
+  }
+  if (KEEP != request) {
+    // TODO: compute stepping up/down from OutputNumPixels but
+    // clamp to inputpixels / 4 (2 steps)
+    cpu_desired_num_pixels_ = static_cast<int>(
+        input_format().width * input_format().height >> cpu_downgrade_count_);
+  }
+  bool changed = AdaptToMinimumFormat();
+  LOG(LS_INFO) << "VAdapt CPU Request: "
+               << (DOWNGRADE == request ? "down" :
+                   (UPGRADE == request ? "up" : "keep"))
+               << " Process: " << process_load
+               << " System: " << system_load
+               << " Steps: " << cpu_downgrade_count_
+               << " Changed: " << (changed ? "true" : "false");
+}
+
+// Called by cpu adapter on up requests.
+bool CoordinatedVideoAdapter::IsMinimumFormat(int pixels) {
+  // Find closest scale factor that matches input resolution to min_num_pixels
+  // and set that for output resolution.  This is not needed for VideoAdapter,
+  // but provides feedback to unittests and users on expected resolution.
+  // Actual resolution is based on input frame.
+  VideoFormat new_output = output_format();
+  VideoFormat input = input_format();
+  if (input_format().IsSize0x0()) {
+    input = new_output;
+  }
+  float scale = 1.0f;
+  if (!input.IsSize0x0()) {
+    scale = FindClosestScale(input.width,
+                             input.height,
+                             pixels);
+  }
+  new_output.width = static_cast<int>(input.width * scale);
+  new_output.height = static_cast<int>(input.height * scale);
+  int new_pixels = new_output.width * new_output.height;
+  int num_pixels = GetOutputNumPixels();
+  return new_pixels <= num_pixels;
+}
+
+// Called by all coordinators when there is a change.
+bool CoordinatedVideoAdapter::AdaptToMinimumFormat() {
+  int old_num_pixels = GetOutputNumPixels();
+  // Get the min of the formats that the server, encoder, and cpu wants.
+  int min_num_pixels = view_desired_num_pixels_;
+  if (encoder_desired_num_pixels_ &&
+      (encoder_desired_num_pixels_ < min_num_pixels)) {
+    min_num_pixels = encoder_desired_num_pixels_;
+  }
+  if (cpu_adaptation_ && cpu_desired_num_pixels_ &&
+      (cpu_desired_num_pixels_ < min_num_pixels)) {
+    min_num_pixels = cpu_desired_num_pixels_;
+    // Update the cpu_downgrade_wait_time_ if we are going to downgrade video.
+    cpu_downgrade_wait_time_ =
+      talk_base::TimeAfter(kDefaultDowngradeWaitTimeMs);
+  }
+  // prevent going below QQVGA
+  if (min_num_pixels > 0 && min_num_pixels < kMinNumPixels) {
+    min_num_pixels = kMinNumPixels;
+  }
+  SetOutputNumPixels(min_num_pixels);
+
+  // Find closest scale factor that matches input resolution to min_num_pixels
+  // and set that for output resolution.  This is not needed for VideoAdapter,
+  // but provides feedback to unittests and users on expected resolution.
+  // Actual resolution is based on input frame.
+  VideoFormat new_output = output_format();
+  VideoFormat input = input_format();
+  if (input_format().IsSize0x0()) {
+    input = new_output;
+  }
+  float scale = 1.0f;
+  if (!input.IsSize0x0()) {
+    scale = FindClosestScale(input.width,
+                             input.height,
+                             min_num_pixels);
+  }
+  new_output.width = static_cast<int>(input.width * scale);
+  new_output.height = static_cast<int>(input.height * scale);
+  new_output.interval = view_desired_interval_;
+  SetOutputFormat(new_output);
+  int new_num_pixels = GetOutputNumPixels();
+  bool changed = new_num_pixels != old_num_pixels;
+
+  LOG(LS_VERBOSE) << "VAdapt Status View: " << view_desired_num_pixels_
+                  << " GD: " << encoder_desired_num_pixels_
+                  << " CPU: " << cpu_desired_num_pixels_
+                  << " Pixels: " << min_num_pixels
+                  << " Scale: " << scale
+                  << " Resolution: " << new_output.width
+                  << "x" << new_output.height
+                  << " Changed: " << (changed ? "true" : "false");
+  return changed;
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/videoadapter.h b/talk/session/phone/videoadapter.h
new file mode 100644
index 0000000..6c14255
--- /dev/null
+++ b/talk/session/phone/videoadapter.h
@@ -0,0 +1,145 @@
+// libjingle
+// Copyright 2010 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_SESSION_PHONE_VIDEOADAPTER_H_
+#define TALK_SESSION_PHONE_VIDEOADAPTER_H_
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/videocommon.h"
+
+namespace cricket {
+
+class VideoFrame;
+
+// VideoAdapter adapts an input video frame to an output frame based on the
+// specified input and output formats. The adaptation includes dropping frames
+// to reduce frame rate and scaling frames. VideoAdapter is thread safe.
+class VideoAdapter {
+ public:
+  VideoAdapter();
+  virtual ~VideoAdapter();
+
+  void SetInputFormat(const VideoFormat& format);
+  void SetOutputFormat(const VideoFormat& format);
+  // Constrain output resolution to this many pixels overall
+  void SetOutputNumPixels(int num_pixels);
+  int GetOutputNumPixels() const;
+
+  const VideoFormat& input_format();
+  const VideoFormat& output_format();
+  // If the parameter black is true, the adapted frames will be black.
+  void SetBlackOutput(bool black);
+
+  // Adapt the input frame from the input format to the output format. Return
+  // true and set the output frame to NULL if the input frame is dropped. Return
+  // true and set the out frame to output_frame_ if the input frame is adapted
+  // successfully. Return false otherwise.
+  // output_frame_ is owned by the VideoAdapter that has the best knowledge on
+  // the output frame.
+  bool AdaptFrame(const VideoFrame* in_frame, const VideoFrame** out_frame);
+
+ protected:
+  float FindClosestScale(int width, int height, int target_num_pixels);
+
+ private:
+  bool StretchToOutputFrame(const VideoFrame* in_frame);
+
+  VideoFormat input_format_;
+  VideoFormat output_format_;
+  int output_num_pixels_;
+  bool black_output_;  // Flag to tell if we need to black output_frame_.
+  bool is_black_;  // Flag to tell if output_frame_ is currently black.
+  int64 drop_frame_count_;
+  talk_base::scoped_ptr<VideoFrame> output_frame_;
+  // The critical section to protect the above variables.
+  talk_base::CriticalSection critical_section_;
+
+  DISALLOW_COPY_AND_ASSIGN(VideoAdapter);
+};
+
+// CoordinatedVideoAdapter adapts the video input to the encoder by coordinating
+// the format request from the server, the resolution request from the encoder,
+// and the CPU load.
+class CoordinatedVideoAdapter
+    : public VideoAdapter, public sigslot::has_slots<>  {
+ public:
+  enum AdaptRequest { UPGRADE, KEEP, DOWNGRADE };
+
+  CoordinatedVideoAdapter();
+  virtual ~CoordinatedVideoAdapter() {}
+
+  // Enable or disable video adaptation due to the change of the CPU load.
+  void set_cpu_adaptation(bool enable) { cpu_adaptation_ = enable; }
+  bool cpu_adaptation() const { return cpu_adaptation_; }
+  // Enable or disable video adaptation due to the change of the GD
+  void set_gd_adaptation(bool enable) { gd_adaptation_ = enable; }
+  bool gd_adaptation() const { return gd_adaptation_; }
+  // Enable or disable video adaptation due to the change of the View
+  void set_view_adaptation(bool enable) { view_adaptation_ = enable; }
+  bool view_adaptation() const { return view_adaptation_; }
+  // When the video is decreased, set the waiting time for CPU adaptation to
+  // decrease video again.
+  void set_cpu_downgrade_wait_time(uint32 ms) { cpu_downgrade_wait_time_ = ms; }
+  // Handle the format request from the server via Jingle update message.
+  void OnOutputFormatRequest(const VideoFormat& format);
+  // Handle the resolution request from the encoder due to bandwidth changes.
+  void OnEncoderResolutionRequest(int width, int height, AdaptRequest request);
+  // Handle the CPU load provided by a CPU monitor.
+  void OnCpuLoadUpdated(int current_cpus, int max_cpus,
+                        float process_load, float system_load);
+
+ private:
+  // Adapt to the minimum of the formats the server requests, the CPU wants, and
+  // the encoder wants.  Returns true if resolution changed.
+  bool AdaptToMinimumFormat();
+  bool IsMinimumFormat(int pixels);
+  void StepPixelCount(CoordinatedVideoAdapter::AdaptRequest request,
+                      int* num_pixels);
+  CoordinatedVideoAdapter::AdaptRequest FindCpuRequest(
+    int current_cpus, int max_cpus,
+    float process_load, float system_load);
+
+  bool cpu_adaptation_;  // True if cpu adaptation is enabled.
+  bool gd_adaptation_;  // True if gd adaptation is enabled.
+  bool view_adaptation_;  // True if view adaptation is enabled.
+  int cpu_downgrade_count_;
+  int cpu_downgrade_wait_time_;
+  // Video formats that the server view requests, the CPU wants, and the encoder
+  // wants respectively. The adapted output format is the minimum of these.
+  int view_desired_num_pixels_;
+  int64 view_desired_interval_;
+  int encoder_desired_num_pixels_;
+  int cpu_desired_num_pixels_;
+  // The critical section to protect handling requests.
+  talk_base::CriticalSection request_critical_section_;
+
+  DISALLOW_COPY_AND_ASSIGN(CoordinatedVideoAdapter);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_VIDEOADAPTER_H_
diff --git a/talk/session/phone/videocapturer.cc b/talk/session/phone/videocapturer.cc
index d62bae8..7f86293 100644
--- a/talk/session/phone/videocapturer.cc
+++ b/talk/session/phone/videocapturer.cc
@@ -34,6 +34,7 @@
 namespace cricket {
 
 static const int64 kMaxDistance = ~(static_cast<int64>(1) << 63);
+static const int64  kMinDesirableFps = static_cast<int64>(15);
 
 /////////////////////////////////////////////////////////////////////
 // Implementation of struct CapturedFrame
@@ -51,7 +52,7 @@
       data(NULL) {
 }
 
-// TODO(fbarchard): Remove this function once lmimediaengine stops using it.
+// TODO: Remove this function once lmimediaengine stops using it.
 bool CapturedFrame::GetDataSize(uint32* size) const {
   if (!size || data_size == CapturedFrame::kUnknownDataSize) {
     return false;
@@ -94,7 +95,7 @@
   std::vector<VideoFormat>::const_iterator i;
   for (i = supported_formats_->begin(); i != supported_formats_->end(); ++i) {
     int64 distance = GetFormatDistance(format, *i);
-    // TODO(fbarchard): Reduce to LS_VERBOSE if/when camera capture is
+    // TODO: Reduce to LS_VERBOSE if/when camera capture is
     // relatively bug free.
     LOG(LS_INFO) << " Supported " << i->ToString()
                  << " distance " << distance;
@@ -104,6 +105,7 @@
     }
   }
   if (supported_formats_->end() == best) {
+    LOG(LS_ERROR) << " No acceptable camera format found";
     return false;
   }
 
@@ -155,10 +157,33 @@
   }
 
   // Check resolution and fps.
-  int desired_height = desired.height;
   int desired_width = desired.width;
-  int64 delta_w = supported.width - desired.width;
-  int64 delta_fps = VideoFormat::IntervalToFps(supported.interval) -
+  int desired_height = desired.height;
+#ifdef OSX
+  // QVGA on OSX is not well supported.  For 16x10, if 320x240 is used, it has
+  // 15x11 pixel aspect ratio on logitech B910/C260 and others.  ComputeCrop
+  // in mediaengine does not crop, so we keep 320x240, which magiccam on Mac
+  // can not display.  Some other viewers can display 320x240, but do not
+  // support pixel aspect ratio and appear distorted.
+  // This code below bumps the preferred resolution to VGA, maintaining aspect
+  // ratio. ie 320x200 -> 640x400.  VGA on logitech and most cameras is 1x1
+  // pixel aspect ratio.  The camera will capture 640x480, ComputeCrop will
+  // crop to 640x400, and the adapter will scale down to QVGA due to JUP view
+  // request.
+  static const int kMinWidth = 640;
+  if (desired_width > 0 && desired_width < kMinWidth) {
+    int new_desired_height = desired_height * kMinWidth / desired_width;
+    LOG(LS_VERBOSE) << " Changed desired from "
+                    << desired_width << "x" << desired_height
+                    << " To "
+                    << kMinWidth << "x" << new_desired_height;
+    desired_width = kMinWidth;
+    desired_height = new_desired_height;
+  }
+#endif
+  int64 delta_w = supported.width - desired_width;
+  int64 supported_fps = VideoFormat::IntervalToFps(supported.interval);
+  int64 delta_fps = supported_fps -
       VideoFormat::IntervalToFps(desired.interval);
   // Check height of supported height compared to height we would like it to be.
   int64 aspect_h = desired_width ?
@@ -172,7 +197,7 @@
   // strongly avoiding going down in resolution, similar to
   // the old method, but not completely ruling it out in extreme situations.
   // It also ignores framerate, which is often very low at high resolutions.
-  // TODO(fbarchard): Improve logic to use weighted factors.
+  // TODO: Improve logic to use weighted factors.
   static const int kDownPenalty = -3;
   if (delta_w < 0) {
     delta_w = delta_w * kDownPenalty;
@@ -183,8 +208,12 @@
   if (delta_fps < 0) {
     // For same resolution, prefer higher framerate but accept lower.
     // Otherwise prefer higher resolution.
-    distance |= static_cast<int64>(1) << 15;
     delta_fps = -delta_fps;
+    if (supported_fps < kMinDesirableFps) {
+      distance |= static_cast<int64>(1) << 62;
+    } else {
+      distance |= static_cast<int64>(1) << 15;
+    }
   }
 
   // 12 bits for width and height and 8 bits for fps and fourcc.
diff --git a/talk/session/phone/videocapturer_unittest.cc b/talk/session/phone/videocapturer_unittest.cc
new file mode 100644
index 0000000..5bd2419
--- /dev/null
+++ b/talk/session/phone/videocapturer_unittest.cc
@@ -0,0 +1,464 @@
+// Copyright 2008 Google Inc. All Rights Reserved
+
+#include <stdio.h>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/videocapturer.h"
+#include "talk/session/phone/testutils.h"
+
+namespace {
+
+class FakeVideoCapturer : public cricket::VideoCapturer {
+ public:
+  FakeVideoCapturer() {
+    // Default supported formats. Use ResetSupportedFormats to over write.
+    std::vector<cricket::VideoFormat> formats;
+    formats.push_back(cricket::VideoFormat(640, 480,
+        cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+    formats.push_back(cricket::VideoFormat(320, 240,
+        cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+    ResetSupportedFormats(formats);
+  }
+  ~FakeVideoCapturer() {}
+
+  void ResetSupportedFormats(const std::vector<cricket::VideoFormat>& formats) {
+    SetSupportedFormats(formats);
+  }
+  virtual cricket::CaptureResult Start(const cricket::VideoFormat& format) {
+    return cricket::CR_SUCCESS;
+  }
+  virtual void Stop() {}
+  virtual bool IsRunning() { return true; }
+  bool GetPreferredFourccs(std::vector<uint32>* fourccs) {
+    fourccs->push_back(cricket::FOURCC_I420);
+    fourccs->push_back(cricket::FOURCC_MJPG);
+    return true;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FakeVideoCapturer);
+};
+
+TEST(VideoCapturerTest, TestFourccMatch) {
+  FakeVideoCapturer capturer;
+  cricket::VideoFormat desired(640, 480,
+                               cricket::VideoFormat::FpsToInterval(30),
+                               cricket::FOURCC_ANY);
+  cricket::VideoFormat best;
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.fourcc = cricket::FOURCC_MJPG;
+  EXPECT_FALSE(capturer.GetBestCaptureFormat(desired, &best));
+
+  desired.fourcc = cricket::FOURCC_I420;
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+}
+
+TEST(VideoCapturerTest, TestResolutionMatch) {
+  FakeVideoCapturer capturer;
+  cricket::VideoFormat desired(960, 720,
+                               cricket::VideoFormat::FpsToInterval(30),
+                               cricket::FOURCC_ANY);
+  cricket::VideoFormat best;
+  // Ask for 960x720. Get VGA which is the highest.
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 360;
+  desired.height = 250;
+  // Ask for a little higher than QVGA. Get QVGA.  On OSX gets VGA
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+#else
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+#endif
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 480;
+  desired.height = 270;
+  // Ask for HVGA. Get VGA.
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 320;
+  desired.height = 240;
+  // Ask for QVGA. Get QVGA.  On OSX get VGA
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+#else
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+#endif
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 160;
+  desired.height = 120;
+  // Ask for lower than QVGA. Get QVGA, which is the lowest.  OSX gets VGA
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+#else
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+#endif
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+}
+
+TEST(VideoCapturerTest, TestHDResolutionMatch) {
+  FakeVideoCapturer capturer;
+
+  // Add some HD formats
+  std::vector<cricket::VideoFormat> formats;
+  formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  formats.push_back(cricket::VideoFormat(960, 544,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  formats.push_back(cricket::VideoFormat(2592, 1944,
+      cricket::VideoFormat::FpsToInterval(15), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(formats);
+
+  cricket::VideoFormat desired(960, 720,
+                               cricket::VideoFormat::FpsToInterval(30),
+                               cricket::FOURCC_ANY);
+  cricket::VideoFormat best;
+  // Ask for 960x720. Get qHD
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(960, best.width);
+  EXPECT_EQ(544, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 360;
+  desired.height = 250;
+  // Ask for a litter higher than QVGA. Get QVGA.  OSX gets VGA
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+#else
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+#endif
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 480;
+  desired.height = 270;
+  // Ask for HVGA. Get VGA.
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 320;
+  desired.height = 240;
+  // Ask for QVGA. Get QVGA.  OSX gets VGA
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+#else
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+#endif
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 160;
+  desired.height = 120;
+  // Ask for lower than QVGA. Get QVGA, which is the lowest.
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+#else
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+#endif
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 1280;
+  desired.height = 720;
+  // Ask for HD. Get qHD.
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(960, best.width);
+  EXPECT_EQ(544, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 1920;
+  desired.height = 1080;
+  // Ask for 1080p. Get 2592x1944x15.
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(2592, best.width);
+  EXPECT_EQ(1944, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(15), best.interval);
+}
+
+// Some cameras support 320x240 and 320x640. Verify we choose 320x240.
+// On OSX we choose VGA
+TEST(VideoCapturerTest, TestStrangeFormats) {
+  FakeVideoCapturer capturer;
+  std::vector<cricket::VideoFormat> supported_formats;
+  supported_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(320, 640,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  std::vector<cricket::VideoFormat> required_formats;
+  required_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  required_formats.push_back(cricket::VideoFormat(320, 200,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  required_formats.push_back(cricket::VideoFormat(320, 180,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  cricket::VideoFormat best;
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+    EXPECT_EQ(320, best.width);
+    EXPECT_EQ(240, best.height);
+  }
+
+  supported_formats.clear();
+  supported_formats.push_back(cricket::VideoFormat(320, 640,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+    EXPECT_EQ(320, best.width);
+    EXPECT_EQ(240, best.height);
+  }
+}
+
+// Some cameras only have very low fps. Verify we choose something sensible.
+TEST(VideoCapturerTest, TestPoorFpsFormats) {
+  FakeVideoCapturer capturer;
+  // all formats are low framerate
+  std::vector<cricket::VideoFormat> supported_formats;
+  supported_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(10), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(7), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(1280, 720,
+      cricket::VideoFormat::FpsToInterval(2), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  std::vector<cricket::VideoFormat> required_formats;
+  required_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  required_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  cricket::VideoFormat best;
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+#ifdef OSX
+    EXPECT_EQ(640, best.width);
+    EXPECT_EQ(480, best.height);
+#else
+    EXPECT_EQ(required_formats[i].width, best.width);
+    EXPECT_EQ(required_formats[i].height, best.height);
+#endif
+  }
+
+  // Increase framerate of 320x240.  Expect low fps VGA avoided.
+  // Except on Mac, where QVGA is avoid due to aspect ratio.
+  supported_formats.clear();
+  supported_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(15), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(7), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(1280, 720,
+      cricket::VideoFormat::FpsToInterval(2), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+    EXPECT_EQ(320, best.width);
+    EXPECT_EQ(240, best.height);
+  }
+}
+
+// Some cameras support same size with different frame rates. Verify we choose
+// the frame rate properly.
+TEST(VideoCapturerTest, TestSameSizeDifferentFpsFormats) {
+  FakeVideoCapturer capturer;
+  std::vector<cricket::VideoFormat> supported_formats;
+  supported_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(10), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(20), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  std::vector<cricket::VideoFormat> required_formats = supported_formats;
+  cricket::VideoFormat best;
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+    EXPECT_EQ(320, best.width);
+    EXPECT_EQ(240, best.height);
+    EXPECT_EQ(required_formats[i].interval, best.interval);
+  }
+}
+
+// Some cameras support the correct resolution but at a lower fps than
+// we'd like.  This tests we get the expected resolution and fps.
+TEST(VideoCapturerTest, TestFpsFormats) {
+  FakeVideoCapturer capturer;
+  // We have VGA but low fps.  Choose VGA, not HD
+  std::vector<cricket::VideoFormat> supported_formats;
+  supported_formats.push_back(cricket::VideoFormat(1280, 720,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(15), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 400,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 360,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  std::vector<cricket::VideoFormat> required_formats;
+  required_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_ANY));
+  required_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(20), cricket::FOURCC_ANY));
+  required_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(10), cricket::FOURCC_ANY));
+  cricket::VideoFormat best;
+
+  // expect 30 fps to choose 15 fps format
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[0], &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(15), best.interval);
+
+  // expect 20 fps to choose 15 fps format
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[1], &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(15), best.interval);
+
+  // expect 10 fps to choose 15 fps format but set fps to 10
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[2], &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(10), best.interval);
+
+  // We have VGA 60 fps and 15 fps.  Choose best fps.
+  supported_formats.clear();
+  supported_formats.push_back(cricket::VideoFormat(1280, 720,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(60), cricket::FOURCC_MJPG));
+  supported_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(15), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 400,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 360,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  // expect 30 fps to choose 60 fps format, but will set best fps to 30
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[0], &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  // expect 20 fps to choose 60 fps format, but will set best fps to 20
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[1], &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(20), best.interval);
+
+  // expect 10 fps to choose 10 fps
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[2], &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(10), best.interval);
+}
+
+TEST(VideoCapturerTest, TestRequest16x10_9) {
+  FakeVideoCapturer capturer;
+  std::vector<cricket::VideoFormat> supported_formats;
+  // We do not support HD, expect 4x3 for 4x3, 16x10, and 16x9 requests.
+  supported_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 400,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 360,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  std::vector<cricket::VideoFormat> required_formats = supported_formats;
+  cricket::VideoFormat best;
+  // Expect 4x3 for 4x3, 16x10, and 16x9 requests.
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+    EXPECT_EQ(640, best.width);
+    EXPECT_EQ(480, best.height);
+  }
+
+  // We do not support 16x9 HD, expect 4x3 for 4x3, 16x10, and 16x9 requests.
+  supported_formats.clear();
+  supported_formats.push_back(cricket::VideoFormat(960, 720,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 400,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 360,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  // Expect 4x3 for 4x3, 16x10, and 16x9 requests.
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+    EXPECT_EQ(640, best.width);
+    EXPECT_EQ(480, best.height);
+  }
+
+  // We support 16x9HD, expect 4x3 for 4x3 and 16x10 requests and expect 16x9
+  // for 16x9 request.
+  supported_formats.clear();
+  supported_formats.push_back(cricket::VideoFormat(1280, 720,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 400,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 360,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  // Expect 4x3 for 4x3 and 16x10 requests.
+  for (size_t i = 0; i < required_formats.size() - 1; ++i) {
+    EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+    EXPECT_EQ(640, best.width);
+    EXPECT_EQ(480, best.height);
+  }
+
+  // Expect 16x9 for 16x9 request.
+  EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[2], &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(360, best.height);
+}
+
+}  // unnamed namespace
diff --git a/talk/session/phone/videocommon.cc b/talk/session/phone/videocommon.cc
new file mode 100644
index 0000000..0d07ef1
--- /dev/null
+++ b/talk/session/phone/videocommon.cc
@@ -0,0 +1,90 @@
+// libjingle
+// Copyright 2010 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "talk/session/phone/videocommon.h"
+
+#include <sstream>
+
+#include "talk/base/common.h"
+
+namespace cricket {
+
+struct FourCCAliasEntry {
+  uint32 alias;
+  uint32 canonical;
+};
+
+static const FourCCAliasEntry kFourCCAliases[] = {
+  {FOURCC_IYUV, FOURCC_I420},
+  {FOURCC_YU12, FOURCC_I420},
+  {FOURCC_YU16, FOURCC_I422},
+  {FOURCC_YU24, FOURCC_I444},
+  {FOURCC_YUYV, FOURCC_YUY2},
+  {FOURCC_YUVS, FOURCC_YUY2},
+  {FOURCC_HDYC, FOURCC_UYVY},
+  {FOURCC_2VUY, FOURCC_UYVY},
+  {FOURCC_BA81, FOURCC_BGGR},
+  {FOURCC_JPEG, FOURCC_MJPG},  // Note: JPEG has DHT while MJPG does not.
+  {FOURCC_DMB1, FOURCC_MJPG},
+  {FOURCC_RGB3, FOURCC_RAW},
+  {FOURCC_BGR3, FOURCC_24BG},
+};
+
+uint32 CanonicalFourCC(uint32 fourcc) {
+  for (int i = 0; i < ARRAY_SIZE(kFourCCAliases); ++i) {
+    if (kFourCCAliases[i].alias == fourcc) {
+      return kFourCCAliases[i].canonical;
+    }
+  }
+  // Not an alias, so return it as-is.
+  return fourcc;
+}
+
+// The C++ standard requires a namespace-scope definition of static const
+// integral types even when they are initialized in the declaration (see
+// [class.static.data]/4), but MSVC with /Ze is non-conforming and treats that
+// as a multiply defined symbol error (see
+// http://msdn.microsoft.com/en-us/library/34h23df8.aspx).
+#ifndef _MSC_EXTENSIONS
+const int64 VideoFormat::kMinimumInterval;  // Initialized in header.
+#endif
+
+std::string VideoFormat::ToString() const {
+  std::string fourcc_name = GetFourccName(fourcc) + " ";
+  for (std::string::const_iterator i = fourcc_name.begin();
+      i < fourcc_name.end(); ++i) {
+    // Test character is printable; Avoid isprint() which asserts on negatives
+    if (*i < 32 || *i >= 127) {
+      fourcc_name = "";
+      break;
+    }
+  }
+
+  std::ostringstream ss;
+  ss << fourcc_name << width << "x" << height << "x" << IntervalToFps(interval);
+  return ss.str();
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/videocommon.h b/talk/session/phone/videocommon.h
index 995e800..fa695f0 100644
--- a/talk/session/phone/videocommon.h
+++ b/talk/session/phone/videocommon.h
@@ -31,37 +31,34 @@
 #include <string>
 
 #include "talk/base/basictypes.h"
+#include "talk/base/timeutils.h"
 
 namespace cricket {
 
 //////////////////////////////////////////////////////////////////////////////
-// Definition of fourcc.
+// Definition of FourCC codes
 //////////////////////////////////////////////////////////////////////////////
-// Convert four characters to a fourcc code.
+// Convert four characters to a FourCC code.
 // Needs to be a macro otherwise the OS X compiler complains when the kFormat*
 // constants are used in a switch.
-#define FOURCC(a, b, c, d) (\
+#define FOURCC(a, b, c, d) ( \
     (static_cast<uint32>(a)) | (static_cast<uint32>(b) << 8) | \
     (static_cast<uint32>(c) << 16) | (static_cast<uint32>(d) << 24))
 
-// Get the name, that is, string with four characters, of a fourcc code.
-inline std::string GetFourccName(uint32 fourcc) {
-  std::string name;
-  name.push_back(static_cast<char>(fourcc & 0xFF));
-  name.push_back(static_cast<char>((fourcc >> 8) & 0xFF));
-  name.push_back(static_cast<char>((fourcc >> 16) & 0xFF));
-  name.push_back(static_cast<char>((fourcc >> 24) & 0xFF));
-  return name;
-}
-
-// FourCC codes used in Google Talk.
-// Some good pages discussing FourCC codes:
-//   http://developer.apple.com/quicktime/icefloe/dispatch020.html
+// Some pages discussing FourCC codes:
 //   http://www.fourcc.org/yuv.php
+//   http://v4l2spec.bytesex.org/spec/book1.htm
+//   http://developer.apple.com/quicktime/icefloe/dispatch020.html
+
 enum FourCC {
   // Canonical fourcc codes used in our code.
   FOURCC_I420 = FOURCC('I', '4', '2', '0'),
+  FOURCC_I422 = FOURCC('I', '4', '2', '2'),
+  FOURCC_I444 = FOURCC('I', '4', '4', '4'),
+  FOURCC_I400 = FOURCC('I', '4', '0', '0'),
   FOURCC_YV12 = FOURCC('Y', 'V', '1', '2'),
+  FOURCC_YV16 = FOURCC('Y', 'V', '1', '6'),
+  FOURCC_YV24 = FOURCC('Y', 'V', '2', '4'),
   FOURCC_YUY2 = FOURCC('Y', 'U', 'Y', '2'),
   FOURCC_UYVY = FOURCC('U', 'Y', 'V', 'Y'),
   FOURCC_M420 = FOURCC('M', '4', '2', '0'),
@@ -70,6 +67,9 @@
   FOURCC_ABGR = FOURCC('A', 'B', 'G', 'R'),
   FOURCC_BGRA = FOURCC('B', 'G', 'R', 'A'),
   FOURCC_ARGB = FOURCC('A', 'R', 'G', 'B'),
+  FOURCC_RGBP = FOURCC('R', 'G', 'B', 'P'), // bgr565
+  FOURCC_RGBO = FOURCC('R', 'G', 'B', 'O'), // abgr1555
+  FOURCC_R444 = FOURCC('R', '4', '4', '4'), // argb4444
   FOURCC_MJPG = FOURCC('M', 'J', 'P', 'G'),
   FOURCC_RAW  = FOURCC('r', 'a', 'w', ' '),
   FOURCC_NV21 = FOURCC('N', 'V', '2', '1'),
@@ -85,6 +85,8 @@
   // equivalents by CanonicalFourCC().
   FOURCC_IYUV = FOURCC('I', 'Y', 'U', 'V'),  // Alias for I420
   FOURCC_YU12 = FOURCC('Y', 'U', '1', '2'),  // Alias for I420
+  FOURCC_YU16 = FOURCC('Y', 'U', '1', '6'),  // Alias for I422
+  FOURCC_YU24 = FOURCC('Y', 'U', '2', '4'),  // Alias for I444
   FOURCC_YUYV = FOURCC('Y', 'U', 'Y', 'V'),  // Alias for YUY2
   FOURCC_YUVS = FOURCC('y', 'u', 'v', 's'),  // Alias for YUY2 on Mac
   FOURCC_HDYC = FOURCC('H', 'D', 'Y', 'C'),  // Alias for UYVY
@@ -102,39 +104,59 @@
 // Converts fourcc aliases into canonical ones.
 uint32 CanonicalFourCC(uint32 fourcc);
 
+// Get FourCC code as a string
+inline std::string GetFourccName(uint32 fourcc) {
+  std::string name;
+  name.push_back(static_cast<char>(fourcc & 0xFF));
+  name.push_back(static_cast<char>((fourcc >> 8) & 0xFF));
+  name.push_back(static_cast<char>((fourcc >> 16) & 0xFF));
+  name.push_back(static_cast<char>((fourcc >> 24) & 0xFF));
+  return name;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 // Definition of VideoFormat.
 //////////////////////////////////////////////////////////////////////////////
 
-static const int64 kNumNanosecsPerSec = 1000000000;
+// VideoFormat with Plain Old Data for global variables
+struct VideoFormatPod {
+  int width;  // in number of pixels
+  int height;  // in number of pixels
+  int64 interval;  // in nanoseconds
+  uint32 fourcc;  // color space. FOURCC_ANY means that any color space is OK.
+};
 
-struct VideoFormat {
-  static const int64 kMinimumInterval = kNumNanosecsPerSec / 10000;  // 10k fps
+struct VideoFormat : VideoFormatPod{
+  static const int64 kMinimumInterval =
+      talk_base::kNumNanosecsPerSec / 10000;  // 10k fps
 
-  VideoFormat() : width(0), height(0), interval(0), fourcc(0) {}
-
-  VideoFormat(int w, int h, int64 interval_ns, uint32 cc)
-      : width(w),
-        height(h),
-        interval(interval_ns),
-        fourcc(cc) {
+  VideoFormat() {
+    Construct(0, 0, 0, 0);
   }
 
-  VideoFormat(const VideoFormat& format)
-      : width(format.width),
-        height(format.height),
-        interval(format.interval),
-        fourcc(format.fourcc) {
+  VideoFormat(int w, int h, int64 interval_ns, uint32 cc) {
+    Construct(w, h, interval_ns, cc);
+  }
+
+  explicit VideoFormat(const VideoFormatPod& format) {
+    Construct(format.width, format.height, format.interval, format.fourcc);
+  }
+
+  void Construct(int w, int h, int64 interval_ns, uint32 cc) {
+    width = w;
+    height = h;
+    interval = interval_ns;
+    fourcc = cc;
   }
 
   static int64 FpsToInterval(int fps) {
-    return fps ? kNumNanosecsPerSec / fps : kMinimumInterval;
+    return fps ? talk_base::kNumNanosecsPerSec / fps : kMinimumInterval;
   }
 
   static int IntervalToFps(int64 interval) {
     // Normalize the interval first.
     interval = talk_base::_max(interval, kMinimumInterval);
-    return static_cast<int>(kNumNanosecsPerSec / interval);
+    return static_cast<int>(talk_base::kNumNanosecsPerSec / interval);
   }
 
   bool operator==(const VideoFormat& format) const {
@@ -169,11 +191,6 @@
 
   // Get a string presentation in the form of "fourcc width x height x fps"
   std::string ToString() const;
-
-  int    width;     // in number of pixels
-  int    height;    // in number of pixels
-  int64  interval;  // in nanoseconds
-  uint32 fourcc;    // color space. FOURCC_ANY means that any color space is OK.
 };
 
 // Result of video capturer start.
diff --git a/talk/session/phone/videocommon_unittest.cc b/talk/session/phone/videocommon_unittest.cc
new file mode 100644
index 0000000..eb74c35
--- /dev/null
+++ b/talk/session/phone/videocommon_unittest.cc
@@ -0,0 +1,102 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/videocommon.h"
+
+namespace cricket {
+
+TEST(VideoCommonTest, TestCanonicalFourCC) {
+  // Canonical fourccs are not changed.
+  EXPECT_EQ(FOURCC_I420, CanonicalFourCC(FOURCC_I420));
+  // The special FOURCC_ANY value is not changed.
+  EXPECT_EQ(FOURCC_ANY, CanonicalFourCC(FOURCC_ANY));
+  // Aliases are translated to the canonical equivalent.
+  EXPECT_EQ(FOURCC_I420, CanonicalFourCC(FOURCC_IYUV));
+  EXPECT_EQ(FOURCC_I422, CanonicalFourCC(FOURCC_YU16));
+  EXPECT_EQ(FOURCC_I444, CanonicalFourCC(FOURCC_YU24));
+  EXPECT_EQ(FOURCC_24BG, CanonicalFourCC(FOURCC_BGR3));
+  EXPECT_EQ(FOURCC_RAW, CanonicalFourCC(FOURCC_RGB3));
+  EXPECT_EQ(FOURCC_MJPG, CanonicalFourCC(FOURCC_DMB1));
+}
+
+// Test conversion between interval and fps
+TEST(VideoCommonTest, TestVideoFormatFps) {
+  EXPECT_EQ(VideoFormat::kMinimumInterval, VideoFormat::FpsToInterval(0));
+  EXPECT_EQ(talk_base::kNumNanosecsPerSec / 20, VideoFormat::FpsToInterval(20));
+  EXPECT_EQ(20, VideoFormat::IntervalToFps(talk_base::kNumNanosecsPerSec / 20));
+}
+
+// Test IsSize0x0
+TEST(VideoCommonTest, TestVideoFormatIsSize0x0) {
+  VideoFormat format;
+  EXPECT_TRUE(format.IsSize0x0());
+  format.width = 320;
+  EXPECT_FALSE(format.IsSize0x0());
+}
+
+// Test ToString: print fourcc when it is printable.
+TEST(VideoCommonTest, TestVideoFormatToString) {
+  VideoFormat format;
+  EXPECT_EQ("0x0x10000", format.ToString());
+
+  format.fourcc = FOURCC_I420;
+  format.width = 640;
+  format.height = 480;
+  format.interval = VideoFormat::FpsToInterval(20);
+  EXPECT_EQ("I420 640x480x20", format.ToString());
+
+  format.fourcc = FOURCC_ANY;
+  format.width = 640;
+  format.height = 480;
+  format.interval = VideoFormat::FpsToInterval(20);
+  EXPECT_EQ("640x480x20", format.ToString());
+}
+
+// Test comparison.
+TEST(VideoCommonTest, TestVideoFormatCompare) {
+  VideoFormat format(640, 480, VideoFormat::FpsToInterval(20), FOURCC_I420);
+  VideoFormat format2;
+  EXPECT_NE(format, format2);
+
+  // Same pixelrate, different fourcc.
+  format2 = format;
+  format2.fourcc = FOURCC_YUY2;
+  EXPECT_NE(format, format2);
+  EXPECT_FALSE(format.IsPixelRateLess(format2) ||
+               format2.IsPixelRateLess(format2));
+
+  format2 = format;
+  format2.interval /= 2;
+  EXPECT_TRUE(format.IsPixelRateLess(format2));
+
+  format2 = format;
+  format2.width *= 2;
+  EXPECT_TRUE(format.IsPixelRateLess(format2));
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/videoframe.cc b/talk/session/phone/videoframe.cc
new file mode 100644
index 0000000..dd02a9a
--- /dev/null
+++ b/talk/session/phone/videoframe.cc
@@ -0,0 +1,147 @@
+/*
+ * 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/session/phone/videoframe.h"
+
+#include <cstring>
+
+#ifdef HAVE_YUV
+#include "libyuv/planar_functions.h"
+#include "libyuv/scale.h"
+#endif
+
+namespace cricket {
+
+// Round to 2 pixels because Chroma channels are half size.
+#define ROUNDTO2(v) (v & ~1)
+
+// TODO: Handle odd width/height with rounding.
+void VideoFrame::StretchToPlanes(
+    uint8* y, uint8* u, uint8* v,
+    int32 dst_pitch_y, int32 dst_pitch_u, int32 dst_pitch_v,
+    size_t width, size_t height, bool interpolate, bool vert_crop) const {
+#ifdef HAVE_YUV
+  if (!GetYPlane() || !GetUPlane() || !GetVPlane())
+    return;
+
+  const uint8* in_y = GetYPlane();
+  const uint8* in_u = GetUPlane();
+  const uint8* in_v = GetVPlane();
+  int32 iwidth = GetWidth();
+  int32 iheight = GetHeight();
+
+  if (vert_crop) {
+    // Adjust the input width:height ratio to be the same as the output ratio.
+    if (iwidth * height > iheight * width) {
+      // Reduce the input width, but keep size/position aligned for YuvScaler
+      iwidth = ROUNDTO2(iheight * width / height);
+      int32 iwidth_offset = ROUNDTO2((GetWidth() - iwidth) / 2);
+      in_y += iwidth_offset;
+      in_u += iwidth_offset / 2;
+      in_v += iwidth_offset / 2;
+    } else if (iwidth * height < iheight * width) {
+      // Reduce the input height.
+      iheight = iwidth * height / width;
+      int32 iheight_offset = (GetHeight() - iheight) >> 2;
+      iheight_offset <<= 1;  // Ensure that iheight_offset is even.
+      in_y += iheight_offset * GetYPitch();
+      in_u += iheight_offset / 2 * GetUPitch();
+      in_v += iheight_offset / 2 * GetVPitch();
+    }
+  }
+
+  // Scale to the output I420 frame.
+  libyuv::Scale(in_y, in_u, in_v,
+                GetYPitch(),
+                GetUPitch(),
+                GetVPitch(),
+                iwidth, iheight,
+                y, u, v, dst_pitch_y, dst_pitch_u, dst_pitch_v,
+                width, height, interpolate);
+#endif
+}
+
+size_t VideoFrame::StretchToBuffer(size_t w, size_t h,
+                                   uint8* buffer, size_t size,
+                                   bool interpolate, bool vert_crop) const {
+  if (!buffer) return 0;
+
+  size_t needed = SizeOf(w, h);
+  if (needed <= size) {
+    uint8* bufy = buffer;
+    uint8* bufu = bufy + w * h;
+    uint8* bufv = bufu + ((w + 1) >> 1) * ((h + 1) >> 1);
+    StretchToPlanes(bufy, bufu, bufv, w, (w + 1) >> 1, (w + 1) >> 1, w, h,
+                    interpolate, vert_crop);
+  }
+  return needed;
+}
+
+void VideoFrame::StretchToFrame(VideoFrame *target,
+                                bool interpolate, bool vert_crop) const {
+  if (!target) return;
+
+  StretchToPlanes(target->GetYPlane(),
+                  target->GetUPlane(),
+                  target->GetVPlane(),
+                  target->GetYPitch(),
+                  target->GetUPitch(),
+                  target->GetVPitch(),
+                  target->GetWidth(),
+                  target->GetHeight(),
+                  interpolate, vert_crop);
+  target->SetElapsedTime(GetElapsedTime());
+  target->SetTimeStamp(GetTimeStamp());
+}
+
+VideoFrame* VideoFrame::Stretch(size_t w, size_t h,
+                                bool interpolate, bool vert_crop) const {
+  VideoFrame* dest = CreateEmptyFrame(w, h, GetPixelWidth(), GetPixelHeight(),
+                                      GetElapsedTime(), GetTimeStamp());
+  if (dest) {
+    StretchToFrame(dest, interpolate, vert_crop);
+  }
+  return dest;
+}
+
+bool VideoFrame::SetToBlack() {
+#ifdef HAVE_YUV
+  return libyuv::I420Rect(GetYPlane(), GetYPitch(),
+                          GetUPlane(), GetUPitch(),
+                          GetVPlane(), GetVPitch(),
+                          0, 0, GetWidth(), GetHeight(),
+                          16, 128, 128) == 0;
+#else
+  int uv_size = GetUPitch() * GetChromaHeight();
+  memset(GetYPlane(), 16, GetWidth() * GetHeight());
+  memset(GetUPlane(), 128, uv_size);
+  memset(GetVPlane(), 128, uv_size);
+  return true;
+#endif
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/videoframe.h b/talk/session/phone/videoframe.h
index 6f331d1..c34db88 100644
--- a/talk/session/phone/videoframe.h
+++ b/talk/session/phone/videoframe.h
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2004 Google Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -50,13 +50,24 @@
 
  public:
   VideoFrame() : rendered_(false) {}
-
   virtual ~VideoFrame() {}
 
+  // Creates a frame from a raw sample with FourCC |format| and size |w| x |h|.
+  // |h| can be negative indicating a vertically flipped image.
+  // |dw| is destination width; can be less than |w| if cropping is desired.
+  // |dh| is destination height, like |dw|, but must be a positive number.
+  // Returns whether the function succeeded or failed.
+  virtual bool Reset(uint32 fourcc, int w, int h, int dw, int dh,
+                     uint8 *sample, size_t sample_size,
+                     size_t pixel_width, size_t pixel_height,
+                     int64 elapsed_time, int64 time_stamp, int rotation) = 0;
+
+  // Basic accessors.
   virtual size_t GetWidth() const = 0;
   virtual size_t GetHeight() const = 0;
   size_t GetChromaWidth() const { return (GetWidth() + 1) / 2; }
   size_t GetChromaHeight() const { return (GetHeight() + 1) / 2; }
+  size_t GetChromaSize() const { return GetUPitch() * GetChromaHeight(); }
   virtual const uint8 *GetYPlane() const = 0;
   virtual const uint8 *GetUPlane() const = 0;
   virtual const uint8 *GetVPlane() const = 0;
@@ -72,8 +83,6 @@
   virtual size_t GetPixelWidth() const = 0;
   virtual size_t GetPixelHeight() const = 0;
 
-  // TODO: Add a fourcc format here and probably combine VideoFrame
-  // with CapturedFrame.
   virtual int64 GetElapsedTime() const = 0;
   virtual int64 GetTimeStamp() const = 0;
   virtual void SetElapsedTime(int64 elapsed_time) = 0;
@@ -102,10 +111,10 @@
 
   // Converts the I420 data to RGB of a certain type such as ARGB and ABGR.
   // Returns the frame's actual size, regardless of whether it was written or
-  // not (like snprintf). Parameters size and pitch_rgb are in units of bytes.
+  // not (like snprintf). Parameters size and stride_rgb are in units of bytes.
   // If there is insufficient space, nothing is written.
   virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8 *buffer,
-                                    size_t size, size_t pitch_rgb) const = 0;
+                                    size_t size, int stride_rgb) const = 0;
 
   // Writes the frame into the given planes, stretched to the given width and
   // height. The parameter "interpolate" controls whether to interpolate or just
@@ -114,7 +123,7 @@
   virtual void StretchToPlanes(uint8 *y, uint8 *u, uint8 *v,
                                int32 pitchY, int32 pitchU, int32 pitchV,
                                size_t width, size_t height,
-                               bool interpolate, bool crop) const = 0;
+                               bool interpolate, bool crop) const;
 
   // Writes the frame into the given frame buffer, stretched to the given width
   // and height, provided that it is of sufficient size. Returns the frame's
@@ -124,23 +133,23 @@
   // nearest-point. The parameter "crop" controls whether to crop this frame to
   // the aspect ratio of the given dimensions before stretching.
   virtual size_t StretchToBuffer(size_t w, size_t h, uint8 *buffer, size_t size,
-                                 bool interpolate, bool crop) const = 0;
+                                 bool interpolate, bool crop) const;
 
   // Writes the frame into the target VideoFrame, stretched to the size of that
   // frame. The parameter "interpolate" controls whether to interpolate or just
   // take the nearest-point. The parameter "crop" controls whether to crop this
   // frame to the aspect ratio of the target frame before stretching.
   virtual void StretchToFrame(VideoFrame *target, bool interpolate,
-                              bool crop) const = 0;
+                              bool crop) const;
 
   // Stretches the frame to the given size, creating a new VideoFrame object to
   // hold it. The parameter "interpolate" controls whether to interpolate or
   // just take the nearest-point. The parameter "crop" controls whether to crop
   // this frame to the aspect ratio of the given dimensions before stretching.
   virtual VideoFrame *Stretch(size_t w, size_t h, bool interpolate,
-                              bool crop) const = 0;
+                              bool crop) const;
 
-  // Set the video frame to black.
+  // Sets the video frame to black.
   bool SetToBlack();
 
   // Size of an I420 image of given dimensions when stored as a frame buffer.
@@ -149,6 +158,12 @@
   }
 
  protected:
+  // Creates an empty frame.
+  virtual VideoFrame* CreateEmptyFrame(int w, int h,
+                                       size_t pixel_width, size_t pixel_height,
+                                       int64 elapsed_time,
+                                       int64 time_stamp) const = 0;
+
   // The frame needs to be rendered to magiccam only once.
   // TODO: Remove this flag once magiccam rendering is fully replaced
   // by client3d rendering.
diff --git a/talk/session/phone/videoprocessor.h b/talk/session/phone/videoprocessor.h
new file mode 100644
index 0000000..d3b6249
--- /dev/null
+++ b/talk/session/phone/videoprocessor.h
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_VIDEOPROCESSOR_H_
+#define TALK_SESSION_PHONE_VIDEOPROCESSOR_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+
+class VideoProcessor : public sigslot::has_slots<> {
+ public:
+  virtual ~VideoProcessor() {}
+  // Contents of frame may be manipulated by the processor.
+  // The processed data is expected to be the same size as the
+  // original data
+  virtual void OnFrame(uint32 ssrc, VideoFrame* frame) = 0;
+};
+
+}  // namespace cricket
+#endif  // TALK_SESSION_PHONE_VIDEOPROCESSOR_H_
+
diff --git a/talk/session/phone/videorenderer.h b/talk/session/phone/videorenderer.h
index f1fe547..c428d57 100644
--- a/talk/session/phone/videorenderer.h
+++ b/talk/session/phone/videorenderer.h
@@ -28,6 +28,10 @@
 #ifndef TALK_SESSION_PHONE_VIDEORENDERER_H_
 #define TALK_SESSION_PHONE_VIDEORENDERER_H_
 
+#ifdef _DEBUG
+#include <string>
+#endif  // _DEBUG
+
 namespace cricket {
 
 class VideoFrame;
@@ -40,6 +44,11 @@
   virtual bool SetSize(int width, int height, int reserved) = 0;
   // Called when a new frame is available for display.
   virtual bool RenderFrame(const VideoFrame *frame) = 0;
+
+#ifdef _DEBUG
+  // Allow renderer dumping out rendered frames.
+  virtual bool SetDumpPath(const std::string &path) { return true; }
+#endif  // _DEBUG
 };
 
 }  // namespace cricket
diff --git a/talk/session/phone/voiceprocessor.h b/talk/session/phone/voiceprocessor.h
new file mode 100644
index 0000000..39bcfab
--- /dev/null
+++ b/talk/session/phone/voiceprocessor.h
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_VOICEPROCESSOR_H_
+#define TALK_SESSION_PHONE_VOICEPROCESSOR_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/audioframe.h"
+
+namespace cricket {
+
+enum MediaProcessorDirection {
+    MPD_RX = 1 << 0,
+    MPD_TX = 1 << 1,
+    MPD_RX_AND_TX = MPD_RX | MPD_TX,
+};
+
+class VoiceProcessor : public sigslot::has_slots<> {
+ public:
+  virtual ~VoiceProcessor() {}
+  // Contents of frame may be manipulated by the processor.
+  // The processed data is expected to be the same size as the
+  // original data
+  virtual void OnFrame(uint32 ssrc, AudioFrame* frame) = 0;
+};
+
+}  // namespace cricket
+#endif  // TALK_SESSION_PHONE_VOICEPROCESSOR_H_
diff --git a/talk/session/phone/webrtcvideocapturer.cc b/talk/session/phone/webrtcvideocapturer.cc
new file mode 100644
index 0000000..c187238
--- /dev/null
+++ b/talk/session/phone/webrtcvideocapturer.cc
@@ -0,0 +1,368 @@
+// 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.
+//
+// Implementation of class WebRtcVideoCapturer.
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_WEBRTC_VIDEO
+#include "talk/session/phone/webrtcvideocapturer.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/session/phone/webrtcvideoframe.h"
+
+#include "talk/base/win32.h"  // Need this to #include the impl files
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_capture/main/interface/video_capture_factory.h"
+#else
+#include "third_party/webrtc/files/include/video_capture_factory.h"
+#endif
+
+namespace cricket {
+
+struct kVideoFourCCEntry {
+  uint32 fourcc;
+  webrtc::RawVideoType webrtc_type;
+};
+
+// This indicates our format preferences and defines a mapping between
+// webrtc::RawVideoType (from video_capture_defines.h) to our FOURCCs.
+static kVideoFourCCEntry kSupportedFourCCs[] = {
+  { FOURCC_I420, webrtc::kVideoI420 },   // 12 bpp, no conversion
+  { FOURCC_YV12, webrtc::kVideoYV12 },   // 12 bpp, no conversion
+  { FOURCC_NV12, webrtc::kVideoNV12 },   // 12 bpp, fast conversion
+  { FOURCC_NV21, webrtc::kVideoNV21 },   // 12 bpp, fast conversion
+  { FOURCC_YUY2, webrtc::kVideoYUY2 },   // 16 bpp, fast conversion
+  { FOURCC_UYVY, webrtc::kVideoUYVY },   // 16 bpp, fast conversion
+  { FOURCC_MJPG, webrtc::kVideoMJPEG },  // compressed, slow conversion
+  { FOURCC_ARGB, webrtc::kVideoARGB },   // 32 bpp, slow conversion
+  { FOURCC_24BG, webrtc::kVideoRGB24 },  // 32 bpp, slow conversion
+};
+
+class WebRtcVcmFactory : public WebRtcVcmFactoryInterface {
+ public:
+  virtual webrtc::VideoCaptureModule* Create(int id,
+                                             const WebRtc_UWord8* device) {
+    return webrtc::VideoCaptureFactory::Create(id, device);
+  }
+  virtual webrtc::VideoCaptureModule::DeviceInfo* CreateDeviceInfo(int id) {
+    return webrtc::VideoCaptureFactory::CreateDeviceInfo(id);
+  }
+  virtual void DestroyDeviceInfo(webrtc::VideoCaptureModule::DeviceInfo* info) {
+    delete info;
+  }
+};
+
+static bool CapabilityToFormat(const webrtc::VideoCaptureCapability& cap,
+                               VideoFormat* format) {
+  uint32 fourcc = 0;
+  for (size_t i = 0; i < ARRAY_SIZE(kSupportedFourCCs); ++i) {
+    if (kSupportedFourCCs[i].webrtc_type == cap.rawType) {
+      fourcc = kSupportedFourCCs[i].fourcc;
+      break;
+    }
+  }
+  if (fourcc == 0) {
+    return false;
+  }
+
+  format->fourcc = fourcc;
+  format->width = cap.width;
+  format->height = cap.height;
+  format->interval = VideoFormat::FpsToInterval(cap.maxFPS);
+  return true;
+}
+
+static bool FormatToCapability(const VideoFormat& format,
+                               webrtc::VideoCaptureCapability* cap) {
+  webrtc::RawVideoType webrtc_type = webrtc::kVideoUnknown;
+  for (size_t i = 0; i < ARRAY_SIZE(kSupportedFourCCs); ++i) {
+    if (kSupportedFourCCs[i].fourcc == format.fourcc) {
+      webrtc_type = kSupportedFourCCs[i].webrtc_type;
+      break;
+    }
+  }
+  if (webrtc_type == webrtc::kVideoUnknown) {
+    return false;
+  }
+
+  cap->width = format.width;
+  cap->height = format.height;
+  cap->maxFPS = VideoFormat::IntervalToFps(format.interval);
+  cap->expectedCaptureDelay = 0;
+  cap->rawType = webrtc_type;
+  cap->codecType = webrtc::kVideoCodecUnknown;
+  cap->interlaced = false;
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of class WebRtcVideoCapturer
+///////////////////////////////////////////////////////////////////////////
+
+WebRtcVideoCapturer::WebRtcVideoCapturer()
+    : factory_(new WebRtcVcmFactory),
+      module_(NULL),
+      captured_frames_(0) {
+}
+
+WebRtcVideoCapturer::WebRtcVideoCapturer(WebRtcVcmFactoryInterface* factory)
+    : factory_(factory),
+      module_(NULL),
+      captured_frames_(0) {
+}
+
+WebRtcVideoCapturer::~WebRtcVideoCapturer() {
+  if (module_) {
+    module_->Release();
+  }
+}
+
+bool WebRtcVideoCapturer::Init(const Device& device) {
+  if (module_) {
+    LOG(LS_ERROR) << "The capturer is already initialized";
+    return false;
+  }
+
+  webrtc::VideoCaptureModule::DeviceInfo* info = factory_->CreateDeviceInfo(0);
+  if (!info) {
+    return false;
+  }
+
+  // Find the desired camera, by name.
+  // In the future, comparing IDs will be more robust.
+  // TODO: Figure what's needed to allow this.
+  int num_cams = info->NumberOfDevices();
+  WebRtc_UWord8 vcm_id[256] = "";
+  bool found = false;
+  for (int index = 0; index < num_cams; ++index) {
+    WebRtc_UWord8 vcm_name[256];
+    if (info->GetDeviceName(index, vcm_name, ARRAY_SIZE(vcm_name),
+                            vcm_id, ARRAY_SIZE(vcm_id)) != -1) {
+      if (device.name == reinterpret_cast<char*>(vcm_name)) {
+        found = true;
+        break;
+      }
+    }
+  }
+  if (!found) {
+    LOG(LS_WARNING) << "Failed to find capturer for id: " << device.id;
+    factory_->DestroyDeviceInfo(info);
+    return false;
+  }
+
+  // Enumerate the supported formats.
+  // TODO: Find out why this starts/stops the camera...
+  std::vector<VideoFormat> supported;
+  WebRtc_UWord32 num_caps = info->NumberOfCapabilities(vcm_id);
+  for (WebRtc_UWord8 i = 0; i < num_caps; ++i) {
+    webrtc::VideoCaptureCapability cap;
+    if (info->GetCapability(vcm_id, i, cap) != -1) {
+      VideoFormat format;
+      if (CapabilityToFormat(cap, &format)) {
+        supported.push_back(format);
+      } else {
+        LOG(LS_WARNING) << "Ignoring unsupported WebRTC capture format "
+                        << cap.rawType;
+      }
+    }
+  }
+  factory_->DestroyDeviceInfo(info);
+  if (supported.empty()) {
+    LOG(LS_ERROR) << "Failed to find usable formats for id: " << device.id;
+    return false;
+  }
+
+  module_ = factory_->Create(0, vcm_id);
+  if (!module_) {
+    LOG(LS_ERROR) << "Failed to create capturer for id: " << device.id;
+    return false;
+  }
+
+  // It is safe to change member attributes now.
+  module_->AddRef();
+  SetId(device.id);
+  SetSupportedFormats(supported);
+  return true;
+}
+
+bool WebRtcVideoCapturer::Init(webrtc::VideoCaptureModule* module) {
+  if (module_) {
+    LOG(LS_ERROR) << "The capturer is already initialized";
+    return false;
+  }
+  if (!module) {
+    LOG(LS_ERROR) << "Invalid VCM supplied";
+    return false;
+  }
+  // TODO: Set id and formats.
+  (module_ = module)->AddRef();
+  return true;
+}
+
+bool WebRtcVideoCapturer::GetBestCaptureFormat(const VideoFormat& desired,
+                                               VideoFormat* best_format) {
+  if (!best_format) {
+    return false;
+  }
+
+  if (!VideoCapturer::GetBestCaptureFormat(desired, best_format)) {
+    // If the vcm has a list of the supported format, but didn't find the
+    // best match, then we should return fail.
+    if (GetSupportedFormats()) {
+      return false;
+    }
+
+    // We maybe using a manually injected VCM which doesn't support enum.
+    // Use the desired format as the best format.
+    best_format->width = desired.width;
+    best_format->height = desired.height;
+    best_format->fourcc = FOURCC_I420;
+    best_format->interval = desired.interval;
+    LOG(LS_INFO) << "Failed to find best capture format,"
+                 << " fall back to the requested format "
+                 << best_format->ToString();
+  }
+  return true;
+}
+
+CaptureResult WebRtcVideoCapturer::Start(const VideoFormat& capture_format) {
+  if (!module_) {
+    LOG(LS_ERROR) << "The capturer has not been initialized";
+    return CR_NO_DEVICE;
+  }
+
+  if (IsRunning()) {
+    LOG(LS_ERROR) << "The capturer is already running";
+    return CR_FAILURE;
+  }
+
+  SetCaptureFormat(&capture_format);
+
+  webrtc::VideoCaptureCapability cap;
+  if (!FormatToCapability(capture_format, &cap)) {
+    LOG(LS_ERROR) << "Invalid capture format specified";
+    return CR_FAILURE;
+  }
+
+  std::string camera_id(GetId());
+  uint32 start = talk_base::Time();
+  if (module_->RegisterCaptureDataCallback(*this) != 0 ||
+      module_->StartCapture(cap) != 0) {
+    LOG(LS_ERROR) << "Camera '" << camera_id << "' failed to start";
+    return CR_FAILURE;
+  }
+
+  LOG(LS_INFO) << "Camera '" << camera_id << "' started with format "
+               << capture_format.ToString() << ", elapsed time "
+               << talk_base::TimeSince(start) << " ms";
+
+  captured_frames_ = 0;
+  talk_base::Thread::Current()->Post(this);
+  return CR_PENDING;
+}
+
+void WebRtcVideoCapturer::Stop() {
+  if (IsRunning()) {
+    talk_base::Thread::Current()->Clear(this);
+    module_->StopCapture();
+    module_->DeRegisterCaptureDataCallback();
+
+    // TODO: Determine if the VCM exposes any drop stats we can use.
+    double drop_ratio = 0.0;
+    std::string camera_id(GetId());
+    LOG(LS_INFO) << "Camera '" << camera_id << "' stopped after capturing "
+                 << captured_frames_ << " frames and dropping "
+                 << drop_ratio << "%";
+  }
+  SetCaptureFormat(NULL);
+}
+
+bool WebRtcVideoCapturer::IsRunning() {
+  return (module_ != NULL && module_->CaptureStarted());
+}
+
+bool WebRtcVideoCapturer::GetPreferredFourccs(
+    std::vector<uint32>* fourccs) {
+  if (!fourccs) {
+    return false;
+  }
+
+  fourccs->clear();
+  for (size_t i = 0; i < ARRAY_SIZE(kSupportedFourCCs); ++i) {
+    fourccs->push_back(kSupportedFourCCs[i].fourcc);
+  }
+  return true;
+}
+
+void WebRtcVideoCapturer::OnMessage(talk_base::Message* message) {
+  // TODO: Fire SignalCaptureEvent appropriately.
+  SignalStartResult(this, CR_SUCCESS);
+}
+
+void WebRtcVideoCapturer::OnIncomingCapturedFrame(const WebRtc_Word32 id,
+    webrtc::VideoFrame& sample, webrtc::VideoCodecType codec_type) {
+  ASSERT(IsRunning());
+  ASSERT(codec_type == webrtc::kVideoCodecUnknown);
+
+  ++captured_frames_;
+  // Log the size and pixel aspect ratio of the first captured frame.
+  if (1 == captured_frames_) {
+    LOG(LS_INFO) << "Captured frame size "
+                 << sample.Width() << "x" << sample.Height()
+                 << ". Expected format " << GetCaptureFormat()->ToString();
+  }
+
+  // Signal down stream components on captured frame.
+  WebRtcCapturedFrame frame(sample);
+  SignalFrameCaptured(this, &frame);
+}
+
+void WebRtcVideoCapturer::OnCaptureDelayChanged(
+    const WebRtc_Word32 id, const WebRtc_Word32 delay) {
+  LOG(LS_INFO) << "Capture delay changed to " << delay << " ms";
+}
+
+// WebRtcCapturedFrame
+WebRtcCapturedFrame::WebRtcCapturedFrame(const webrtc::VideoFrame& sample) {
+  width = sample.Width();
+  height = sample.Height();
+  fourcc = FOURCC_I420;
+  pixel_width = 1;
+  pixel_height = 1;
+  // convert units from VideoFrame RenderTimeMs
+  // to CapturedFrame (nanoseconds)
+  elapsed_time = sample.RenderTimeMs() * talk_base::kNumNanosecsPerMillisec;
+  time_stamp = elapsed_time;
+  data_size = sample.Length();
+  data = const_cast<WebRtc_UWord8*>(sample.Buffer());
+}
+
+}  // namespace cricket
+
+#endif  // HAVE_WEBRTC_VIDEO
diff --git a/talk/session/phone/webrtcvideocapturer.h b/talk/session/phone/webrtcvideocapturer.h
new file mode 100644
index 0000000..a9cc3e8
--- /dev/null
+++ b/talk/session/phone/webrtcvideocapturer.h
@@ -0,0 +1,102 @@
+// libjingle
+// Copyright 2004 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_SESSION_PHONE_WEBRTCVIDEOCAPTURER_H_
+#define TALK_SESSION_PHONE_WEBRTCVIDEOCAPTURER_H_
+
+#ifdef HAVE_WEBRTC_VIDEO
+
+#include <string>
+#include <vector>
+
+#include "talk/base/messagehandler.h"
+#include "talk/session/phone/videocapturer.h"
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_capture/main/interface/video_capture.h"
+#else
+#include "third_party/webrtc/files/include/video_capture.h"
+#endif
+
+namespace cricket {
+
+// Factory to allow injection of a VCM impl into WebRtcVideoCapturer.
+// DeviceInfos do not have a Release() and therefore need an explicit Destroy().
+class WebRtcVcmFactoryInterface {
+ public:
+  virtual ~WebRtcVcmFactoryInterface() {}
+  virtual webrtc::VideoCaptureModule* Create(
+      int id, const WebRtc_UWord8* device) = 0;
+  virtual webrtc::VideoCaptureModule::DeviceInfo* CreateDeviceInfo(int id) = 0;
+  virtual void DestroyDeviceInfo(
+      webrtc::VideoCaptureModule::DeviceInfo* info) = 0;
+};
+
+// WebRTC-based implementation of VideoCapturer.
+class WebRtcVideoCapturer : public VideoCapturer,
+                            public talk_base::MessageHandler,
+                            public webrtc::VideoCaptureDataCallback {
+ public:
+  WebRtcVideoCapturer();
+  explicit WebRtcVideoCapturer(WebRtcVcmFactoryInterface* factory);
+  virtual ~WebRtcVideoCapturer();
+
+  bool Init(const Device& device);
+  bool Init(webrtc::VideoCaptureModule* module);
+
+  // Override virtual methods of the parent class VideoCapturer.
+  virtual bool GetBestCaptureFormat(const VideoFormat& desired,
+                                    VideoFormat* best_format);
+  virtual CaptureResult Start(const VideoFormat& capture_format);
+  virtual void Stop();
+  virtual bool IsRunning();
+
+ protected:
+  // Override virtual methods of the parent class VideoCapturer.
+  virtual bool GetPreferredFourccs(std::vector<uint32>* fourccs);
+
+ private:
+  // Callback for our started event.
+  virtual void OnMessage(talk_base::Message* message);
+  // Callback when a frame is captured by camera.
+  virtual void OnIncomingCapturedFrame(const WebRtc_Word32 id,
+                                       webrtc::VideoFrame& frame,
+                                       webrtc::VideoCodecType type);
+  virtual void OnCaptureDelayChanged(const WebRtc_Word32 id,
+                                     const WebRtc_Word32 delay);
+
+  talk_base::scoped_ptr<WebRtcVcmFactoryInterface> factory_;
+  webrtc::VideoCaptureModule* module_;
+  int captured_frames_;
+};
+
+struct WebRtcCapturedFrame : public CapturedFrame {
+ public:
+  explicit WebRtcCapturedFrame(const webrtc::VideoFrame& frame);
+};
+
+}  // namespace cricket
+
+#endif  // HAVE_WEBRTC_VIDEO
+#endif  // TALK_SESSION_PHONE_WEBRTCVIDEOCAPTURER_H_
diff --git a/talk/session/phone/webrtcvideocapturer_unittest.cc b/talk/session/phone/webrtcvideocapturer_unittest.cc
new file mode 100644
index 0000000..c883844
--- /dev/null
+++ b/talk/session/phone/webrtcvideocapturer_unittest.cc
@@ -0,0 +1,145 @@
+// 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 <stdio.h>
+#include <vector>
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/fakewebrtcvcmfactory.h"
+#include "talk/session/phone/testutils.h"
+#include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+using cricket::VideoFormat;
+
+static const std::string kTestDeviceName = "JuberTech FakeCam Q123";
+static const std::string kTestDeviceId = "foo://bar/baz";
+const VideoFormat kDefaultVideoFormat =
+    VideoFormat(640, 400, VideoFormat::FpsToInterval(30), cricket::FOURCC_ANY);
+
+class WebRtcVideoCapturerTest : public testing::Test {
+ public:
+  WebRtcVideoCapturerTest()
+      : factory_(new FakeWebRtcVcmFactory),
+        capturer_(new cricket::WebRtcVideoCapturer(factory_)),
+        listener_(capturer_.get()) {
+    factory_->device_info.AddDevice(kTestDeviceName, kTestDeviceId);
+    // add a VGA/I420 capability
+    webrtc::VideoCaptureCapability vga;
+    vga.width = 640;
+    vga.height = 480;
+    vga.maxFPS = 30;
+    vga.rawType = webrtc::kVideoI420;
+    factory_->device_info.AddCapability(kTestDeviceId, vga);
+  }
+
+ protected:
+  FakeWebRtcVcmFactory* factory_;  // owned by capturer_
+  talk_base::scoped_ptr<cricket::WebRtcVideoCapturer> capturer_;
+  cricket::VideoCapturerListener listener_;
+};
+
+TEST_F(WebRtcVideoCapturerTest, TestNotOpened) {
+  EXPECT_EQ("", capturer_->GetId());
+  EXPECT_EQ(NULL, capturer_->GetSupportedFormats());
+  EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);
+  EXPECT_FALSE(capturer_->IsRunning());
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestBadInit) {
+  EXPECT_FALSE(capturer_->Init(cricket::Device("bad-name", "bad-id")));
+  EXPECT_FALSE(capturer_->IsRunning());
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestInit) {
+  EXPECT_TRUE(capturer_->Init(cricket::Device(kTestDeviceName, kTestDeviceId)));
+  EXPECT_EQ(kTestDeviceId, capturer_->GetId());
+  EXPECT_TRUE(NULL != capturer_->GetSupportedFormats());
+  ASSERT_EQ(1U, capturer_->GetSupportedFormats()->size());
+  EXPECT_EQ(640, (*capturer_->GetSupportedFormats())[0].width);
+  EXPECT_EQ(480, (*capturer_->GetSupportedFormats())[0].height);
+  EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);  // not started yet
+  EXPECT_FALSE(capturer_->IsRunning());
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestInitVcm) {
+  EXPECT_TRUE(capturer_->Init(factory_->Create(0,
+      reinterpret_cast<const WebRtc_UWord8*>(kTestDeviceId.c_str()))));
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestCapture) {
+  EXPECT_TRUE(capturer_->Init(cricket::Device(kTestDeviceName, kTestDeviceId)));
+  cricket::VideoFormat format(
+      capturer_->GetSupportedFormats()->at(0));
+  EXPECT_EQ(cricket::CR_PENDING, capturer_->Start(format));
+  EXPECT_TRUE(capturer_->IsRunning());
+  ASSERT_TRUE(capturer_->GetCaptureFormat() != NULL);
+  EXPECT_EQ(format, *capturer_->GetCaptureFormat());
+  EXPECT_EQ_WAIT(cricket::CR_SUCCESS, listener_.start_result(), 1000);
+  EXPECT_TRUE(factory_->modules[0]->SendFrame(640, 480));
+  EXPECT_TRUE_WAIT(listener_.frame_count() > 0, 5000);
+  EXPECT_EQ(capturer_->GetCaptureFormat()->fourcc, listener_.frame_fourcc());
+  EXPECT_EQ(640, listener_.frame_width());
+  EXPECT_EQ(480, listener_.frame_height());
+  EXPECT_EQ(cricket::CR_FAILURE, capturer_->Start(format));
+  capturer_->Stop();
+  EXPECT_FALSE(capturer_->IsRunning());
+  EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestCaptureVcm) {
+  EXPECT_TRUE(capturer_->Init(factory_->Create(0,
+      reinterpret_cast<const WebRtc_UWord8*>(kTestDeviceId.c_str()))));
+  EXPECT_FALSE(capturer_->GetSupportedFormats());
+  VideoFormat format;
+  EXPECT_TRUE(capturer_->GetBestCaptureFormat(kDefaultVideoFormat, &format));
+  EXPECT_EQ(kDefaultVideoFormat.width, format.width);
+  EXPECT_EQ(kDefaultVideoFormat.height, format.height);
+  EXPECT_EQ(kDefaultVideoFormat.interval, format.interval);
+  EXPECT_EQ(cricket::FOURCC_I420, format.fourcc);
+  EXPECT_EQ(cricket::CR_PENDING, capturer_->Start(format));
+  EXPECT_TRUE(capturer_->IsRunning());
+  ASSERT_TRUE(capturer_->GetCaptureFormat() != NULL);
+  EXPECT_EQ(format, *capturer_->GetCaptureFormat());
+  EXPECT_EQ_WAIT(cricket::CR_SUCCESS, listener_.start_result(), 1000);
+  EXPECT_TRUE(factory_->modules[0]->SendFrame(640, 480));
+  EXPECT_TRUE_WAIT(listener_.frame_count() > 0, 5000);
+  EXPECT_EQ(capturer_->GetCaptureFormat()->fourcc, listener_.frame_fourcc());
+  EXPECT_EQ(640, listener_.frame_width());
+  EXPECT_EQ(480, listener_.frame_height());
+  EXPECT_EQ(cricket::CR_FAILURE, capturer_->Start(format));
+  capturer_->Stop();
+  EXPECT_FALSE(capturer_->IsRunning());
+  EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestCaptureWithoutInit) {
+  cricket::VideoFormat format;
+  EXPECT_EQ(cricket::CR_NO_DEVICE, capturer_->Start(format));
+  EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);
+  EXPECT_FALSE(capturer_->IsRunning());
+}
diff --git a/talk/session/phone/webrtcvideoengine.cc b/talk/session/phone/webrtcvideoengine.cc
index d0d71ad..07ab5e6 100644
--- a/talk/session/phone/webrtcvideoengine.cc
+++ b/talk/session/phone/webrtcvideoengine.cc
@@ -25,18 +25,26 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
 #ifdef HAVE_WEBRTC_VIDEO
 
 #include "talk/session/phone/webrtcvideoengine.h"
 
+#include "talk/base/basictypes.h"
 #include "talk/base/common.h"
 #include "talk/base/buffer.h"
 #include "talk/base/byteorder.h"
 #include "talk/base/logging.h"
 #include "talk/base/stringutils.h"
+#include "talk/session/phone/rtputils.h"
+#include "talk/session/phone/streamparams.h"
 #include "talk/session/phone/videorenderer.h"
 #include "talk/session/phone/webrtcpassthroughrender.h"
 #include "talk/session/phone/webrtcvoiceengine.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
 #include "talk/session/phone/webrtcvideoframe.h"
 #include "talk/session/phone/webrtcvie.h"
 #include "talk/session/phone/webrtcvoe.h"
@@ -44,32 +52,75 @@
 namespace cricket {
 
 static const int kDefaultLogSeverity = talk_base::LS_WARNING;
+
+static const int kMinVideoBitrate = 100;
 static const int kStartVideoBitrate = 300;
-static const int kMaxVideoBitrate = 1000;
+static const int kMaxVideoBitrate = 2000;
+static const int kConferenceModeMaxVideoBitrate = 500;
+
+static const int kVideoMtu = 1200;
+
 static const int kVideoRtpBufferSize = 65536;
 
+static const char kVp8PayloadName[] = "VP8";
+static const char kRedPayloadName[] = "red";
+static const char kFecPayloadName[] = "ulpfec";
+
+static const int kDefaultNumberOfTemporalLayers = 3;
+
+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)) {
+    LOG_V(sev) << tok;
+  }
+}
+
 class WebRtcRenderAdapter : public webrtc::ExternalRenderer {
  public:
   explicit WebRtcRenderAdapter(VideoRenderer* renderer)
-      : renderer_(renderer) {
+      : renderer_(renderer), width_(0), height_(0) {
+  }
+  virtual ~WebRtcRenderAdapter() {
   }
 
+  void SetRenderer(VideoRenderer* renderer) {
+    talk_base::CritScope cs(&crit_);
+    renderer_ = renderer;
+  }
+  // Implementation of webrtc::ExternalRenderer.
   virtual int FrameSizeChange(unsigned int width, unsigned int height,
                               unsigned int /*number_of_streams*/) {
-    if (renderer_ == NULL)
+    talk_base::CritScope cs(&crit_);
+    if (renderer_ == NULL) {
       return 0;
+    }
+    LOG(LS_INFO) << "WebRtcRenderAdapter frame size changed to: "
+                 << width << "x" << height;
     width_ = width;
     height_ = height;
     return renderer_->SetSize(width_, height_, 0) ? 0 : -1;
   }
-
   virtual int DeliverFrame(unsigned char* buffer, int buffer_size,
                            unsigned int time_stamp) {
-    if (renderer_ == NULL)
+    talk_base::CritScope cs(&crit_);
+    frame_rate_tracker_.Update(1);
+    if (renderer_ == NULL) {
       return 0;
+    }
     WebRtcVideoFrame video_frame;
+    // Convert 90K timestamp to ns timestamp.
+    int64 time_stamp_in_ns = (time_stamp / 90) *
+       talk_base::kNumNanosecsPerMillisec;
     video_frame.Attach(buffer, buffer_size, width_, height_,
-                       1, 1, 0, time_stamp, 0);
+                       1, 1, 0, time_stamp_in_ns, 0);
+
+
+    // Sanity check on decoded frame size.
+    if (buffer_size != static_cast<int>(VideoFrame::SizeOf(width_, height_))) {
+      LOG(LS_WARNING) << "WebRtcRenderAdapter received a strange frame size: "
+                      << buffer_size;
+    }
+
     int ret = renderer_->RenderFrame(&video_frame) ? 0 : -1;
     uint8* buffer_temp;
     size_t buffer_size_temp;
@@ -77,71 +128,221 @@
     return ret;
   }
 
-  virtual ~WebRtcRenderAdapter() {}
+  unsigned int width() {
+    talk_base::CritScope cs(&crit_);
+    return width_;
+  }
+  unsigned int height() {
+    talk_base::CritScope cs(&crit_);
+    return height_;
+  }
+  int framerate() {
+    talk_base::CritScope cs(&crit_);
+    return frame_rate_tracker_.units_second();
+  }
+  VideoRenderer* renderer() {
+    talk_base::CritScope cs(&crit_);
+    return renderer_;
+  }
 
  private:
+  talk_base::CriticalSection crit_;
   VideoRenderer* renderer_;
   unsigned int width_;
   unsigned int height_;
+  talk_base::RateTracker frame_rate_tracker_;
+};
+
+class WebRtcDecoderObserver : public webrtc::ViEDecoderObserver {
+ public:
+  explicit WebRtcDecoderObserver(int video_channel)
+       : video_channel_(video_channel),
+         framerate_(0),
+         bitrate_(0),
+         firs_requested_(0) {
+  }
+
+  // virtual functions from VieDecoderObserver.
+  virtual void IncomingCodecChanged(const int videoChannel,
+                                    const webrtc::VideoCodec& videoCodec) {}
+  virtual void IncomingRate(const int videoChannel,
+                            const unsigned int framerate,
+                            const unsigned int bitrate) {
+    ASSERT(video_channel_ == videoChannel);
+    framerate_ = framerate;
+    bitrate_ = bitrate;
+  }
+  virtual void RequestNewKeyFrame(const int videoChannel) {
+    ASSERT(video_channel_ == videoChannel);
+    ++firs_requested_;
+  }
+
+  int framerate() const { return framerate_; }
+  int bitrate() const { return bitrate_; }
+  int firs_requested() const { return firs_requested_; }
+
+ private:
+  int video_channel_;
+  int framerate_;
+  int bitrate_;
+  int firs_requested_;
+};
+
+class WebRtcEncoderObserver : public webrtc::ViEEncoderObserver {
+ public:
+  explicit WebRtcEncoderObserver(int video_channel)
+      : video_channel_(video_channel),
+        framerate_(0),
+        bitrate_(0) {
+  }
+
+  // virtual functions from VieEncoderObserver.
+  virtual void OutgoingRate(const int videoChannel,
+                            const unsigned int framerate,
+                            const unsigned int bitrate) {
+    ASSERT(video_channel_ == videoChannel);
+    framerate_ = framerate;
+    bitrate_ = bitrate;
+  }
+
+  int framerate() const { return framerate_; }
+  int bitrate() const { return bitrate_; }
+
+ private:
+  int video_channel_;
+  int framerate_;
+  int bitrate_;
+};
+
+class WebRtcLocalStreamInfo {
+ public:
+  int width() {
+    talk_base::CritScope cs(&crit_);
+    return width_;
+  }
+  int height() {
+    talk_base::CritScope cs(&crit_);
+    return height_;
+  }
+  int framerate() {
+    talk_base::CritScope cs(&crit_);
+    return rate_tracker_.units_second();
+  }
+
+  void UpdateFrame(int width, int height) {
+    talk_base::CritScope cs(&crit_);
+    width_ = width;
+    height_ = height;
+    rate_tracker_.Update(1);
+  }
+
+ private:
+  talk_base::CriticalSection crit_;
+  unsigned int width_;
+  unsigned int height_;
+  talk_base::RateTracker rate_tracker_;
+};
+
+// WebRtcVideoChannelInfo is a container class with members such as renderer
+// and a decoder observer that is used by receive channels.
+// It must exist as long as the receive channel is connected to renderer or a
+// decoder observer in this class and methods in the class should only be called
+// from the worker thread.
+class WebRtcVideoChannelInfo  {
+ public:
+  explicit WebRtcVideoChannelInfo(int channel_id)
+      : channel_id_(channel_id),
+        render_adapter_(NULL),
+        decoder_observer_(channel_id) {
+  }
+  int channel_id() { return channel_id_; }
+  void SetRenderer(VideoRenderer* renderer) {
+    render_adapter_.SetRenderer(renderer);
+  }
+  WebRtcRenderAdapter* render_adapter() { return &render_adapter_; }
+  WebRtcDecoderObserver* decoder_observer() { return &decoder_observer_; }
+
+ private:
+  int channel_id_;  // Webrtc video channel number.
+  // Renderer for this channel.
+  WebRtcRenderAdapter render_adapter_;
+  WebRtcDecoderObserver decoder_observer_;
 };
 
 const WebRtcVideoEngine::VideoCodecPref
     WebRtcVideoEngine::kVideoCodecPrefs[] = {
-    {"VP8", 120, 0},
+    {kVp8PayloadName, 100, 0},
+    {kRedPayloadName, 101, 1},
+    {kFecPayloadName, 102, 2},
 };
 
+static const int64 kNsPerFrame = 33333333;  // 30fps
+
 // The formats are sorted by the descending order of width. We use the order to
 // find the next format for CPU and bandwidth adaptation.
-const VideoFormat WebRtcVideoEngine::kVideoFormats[] = {
-  // TODO: Understand why we have problem with 16:9 formats.
-  VideoFormat(1280, 800, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(1280, 720, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-  VideoFormat(960, 600, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(960, 540, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-  VideoFormat(640, 400, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(640, 360, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-  VideoFormat(480, 300, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(480, 270, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-  VideoFormat(320, 200, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(320, 180, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-  VideoFormat(240, 150, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(240, 135, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-  VideoFormat(160, 100, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(160, 90, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+const VideoFormatPod WebRtcVideoEngine::kVideoFormats[] = {
+  {1280, 800, kNsPerFrame, FOURCC_ANY},
+  {1280, 720, kNsPerFrame, FOURCC_ANY},
+  {960, 600, kNsPerFrame, FOURCC_ANY},
+  {960, 540, kNsPerFrame, FOURCC_ANY},
+  {640, 400, kNsPerFrame, FOURCC_ANY},
+  {640, 360, kNsPerFrame, FOURCC_ANY},
+  {640, 480, kNsPerFrame, FOURCC_ANY},
+  {480, 300, kNsPerFrame, FOURCC_ANY},
+  {480, 270, kNsPerFrame, FOURCC_ANY},
+  {480, 360, kNsPerFrame, FOURCC_ANY},
+  {320, 200, kNsPerFrame, FOURCC_ANY},
+  {320, 180, kNsPerFrame, FOURCC_ANY},
+  {320, 240, kNsPerFrame, FOURCC_ANY},
+  {240, 150, kNsPerFrame, FOURCC_ANY},
+  {240, 135, kNsPerFrame, FOURCC_ANY},
+  {240, 180, kNsPerFrame, FOURCC_ANY},
+  {160, 100, kNsPerFrame, FOURCC_ANY},
+  {160, 90, kNsPerFrame, FOURCC_ANY},
+  {160, 120, kNsPerFrame, FOURCC_ANY},
 };
 
-// TODO: Understand why 640x400 is not working.
-const VideoFormat WebRtcVideoEngine::kDefaultVideoFormat =
-    VideoFormat(320, 200, VideoFormat::FpsToInterval(30), FOURCC_ANY);
+const VideoFormatPod WebRtcVideoEngine::kDefaultVideoFormat =
+    {640, 400, kNsPerFrame, FOURCC_ANY};
 
-WebRtcVideoEngine::WebRtcVideoEngine()
-    : vie_wrapper_(new ViEWrapper()),
-      voice_engine_(NULL) {
-  Construct();
+WebRtcVideoEngine::WebRtcVideoEngine() {
+  Construct(new ViEWrapper(), new ViETraceWrapper(), NULL);
 }
 
 WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
-                                     ViEWrapper* vie_wrapper)
-    : vie_wrapper_(vie_wrapper),
-      voice_engine_(voice_engine) {
-  Construct();
+                                     ViEWrapper* vie_wrapper) {
+  Construct(vie_wrapper, new ViETraceWrapper(), voice_engine);
 }
 
-void  WebRtcVideoEngine::Construct() {
-  initialized_ = false;
-  capture_id_ = -1;
-  capture_module_ = NULL;
-  external_capture_ = false;
-  log_level_ = kDefaultLogSeverity;
-  capture_started_ = false;
-  render_module_.reset(new WebRtcPassthroughRender());
+WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+                                     ViEWrapper* vie_wrapper,
+                                     ViETraceWrapper* tracing) {
+  Construct(vie_wrapper, tracing, voice_engine);
+}
 
-  ApplyLogging();
-  if (vie_wrapper_->engine()->SetTraceCallback(this) != 0) {
+void WebRtcVideoEngine::Construct(ViEWrapper* vie_wrapper,
+                                  ViETraceWrapper* tracing,
+                                  WebRtcVoiceEngine* voice_engine) {
+  LOG(LS_INFO) << "WebRtcVideoEngine::WebRtcVideoEngine";
+  vie_wrapper_.reset(vie_wrapper);
+  vie_wrapper_base_initialized_ = false;
+  tracing_.reset(tracing);
+  voice_engine_ = voice_engine;
+  initialized_ = false;
+  log_level_ = kDefaultLogSeverity;
+  render_module_.reset(new WebRtcPassthroughRender());
+  local_renderer_w_ = local_renderer_h_ = 0;
+  local_renderer_ = NULL;
+  owns_capturer_ = false;
+  video_capturer_ = NULL;
+  capture_started_ = false;
+
+  ApplyLogging("");
+  if (tracing_->SetTraceCallback(this) != 0) {
     LOG_RTCERR1(SetTraceCallback, this);
   }
 
-  // Set default quality levels for our supported codecs.  We override them here
+  // Set default quality levels for our supported codecs. We override them here
   // if we know your cpu performance is low, and they can be updated explicitly
   // by calling SetDefaultCodec.  For example by a flute preference setting, or
   // by the server with a jec in response to our reported system info.
@@ -149,20 +350,22 @@
                        kVideoCodecPrefs[0].name,
                        kDefaultVideoFormat.width,
                        kDefaultVideoFormat.height,
-                       kDefaultVideoFormat.framerate(), 0);
+                       VideoFormat::IntervalToFps(kDefaultVideoFormat.interval),
+                       0);
   if (!SetDefaultCodec(max_codec)) {
     LOG(LS_ERROR) << "Failed to initialize list of supported codec types";
   }
 }
 
 WebRtcVideoEngine::~WebRtcVideoEngine() {
-  LOG(LS_INFO) << " WebRtcVideoEngine::~WebRtcVideoEngine";
-  vie_wrapper_->engine()->SetTraceCallback(NULL);
-  Terminate();
-  vie_wrapper_.reset();
-  if (capture_module_) {
-    capture_module_->Release();
+  ClearCapturer();
+  LOG(LS_INFO) << "WebRtcVideoEngine::~WebRtcVideoEngine";
+  if (initialized_) {
+    Terminate();
   }
+  tracing_->SetTraceCallback(NULL);
+  // Test to see if the media processor was deregistered properly.
+  ASSERT(SignalMediaFrame.is_empty());
 }
 
 bool WebRtcVideoEngine::Init() {
@@ -180,11 +383,26 @@
 bool WebRtcVideoEngine::InitVideoEngine() {
   LOG(LS_INFO) << "WebRtcVideoEngine::InitVideoEngine";
 
-  if (vie_wrapper_->base()->Init() != 0) {
-    LOG_RTCERR0(Init);
+  // Init WebRTC VideoEngine.
+  if (!vie_wrapper_base_initialized_) {
+    if (vie_wrapper_->base()->Init() != 0) {
+      LOG_RTCERR0(Init);
+      return false;
+    }
+    vie_wrapper_base_initialized_ = true;
+  }
+
+  // Log the VoiceEngine version info.
+  char buffer[1024] = "";
+  if (vie_wrapper_->base()->GetVersion(buffer) != 0) {
+    LOG_RTCERR0(GetVersion);
     return false;
   }
 
+  LOG(LS_INFO) << "WebRtc VideoEngine Version:";
+  LogMultiline(talk_base::LS_INFO, buffer);
+
+  // Hook up to VoiceEngine for sync purposes, if supplied.
   if (!voice_engine_) {
     LOG(LS_WARNING) << "NULL voice engine";
   } else if ((vie_wrapper_->base()->SetVoiceEngine(
@@ -193,69 +411,460 @@
     return false;
   }
 
+  // Register for callbacks from the engine.
   if ((vie_wrapper_->base()->RegisterObserver(*this)) != 0) {
     LOG_RTCERR0(RegisterObserver);
     return false;
   }
 
+  // Register our custom render module.
   if (vie_wrapper_->render()->RegisterVideoRenderModule(
-          *render_module_.get()) != 0) {
+      *render_module_.get()) != 0) {
     LOG_RTCERR0(RegisterVideoRenderModule);
     return false;
   }
 
-  std::sort(video_codecs_.begin(), video_codecs_.end(),
-            &VideoCodec::Preferable);
-
   initialized_ = true;
   return true;
 }
 
-void WebRtcVideoEngine::PerformanceAlarm(const unsigned int cpu_load) {
-  LOG(LS_INFO) << "WebRtcVideoEngine::PerformanceAlarm";
+void WebRtcVideoEngine::Terminate() {
+  LOG(LS_INFO) << "WebRtcVideoEngine::Terminate";
+  initialized_ = false;
+  SetCapture(false);
+
+  if (vie_wrapper_->render()->DeRegisterVideoRenderModule(
+      *render_module_.get()) != 0) {
+    LOG_RTCERR0(DeRegisterVideoRenderModule);
+  }
+
+  if (vie_wrapper_->base()->DeregisterObserver() != 0) {
+    LOG_RTCERR0(DeregisterObserver);
+  }
+
+  if (vie_wrapper_->base()->SetVoiceEngine(NULL) != 0) {
+    LOG_RTCERR0(SetVoiceEngine);
+  }
 }
 
-// Ignore spammy trace messages, mostly from the stats API when we haven't
-// gotten RTCP info yet from the remote side.
-static bool ShouldIgnoreTrace(const std::string& trace) {
-  static const char* kTracesToIgnore[] = {
-    "\tfailed to GetReportBlockInformation",
-    NULL
-  };
-  for (const char* const* p = kTracesToIgnore; *p; ++p) {
-    if (trace.find(*p) == 0) {
+int WebRtcVideoEngine::GetCapabilities() {
+  return VIDEO_RECV | VIDEO_SEND;
+}
+
+bool WebRtcVideoEngine::SetOptions(int options) {
+  return true;
+}
+
+bool WebRtcVideoEngine::SetDefaultEncoderConfig(
+    const VideoEncoderConfig& config) {
+  return SetDefaultCodec(config.max_codec);
+}
+
+// SetDefaultCodec may be called while the capturer is running. For example, a
+// test call is started in a page with QVGA default codec, and then a real call
+// is started in another page with VGA default codec. This is the corner case
+// and happens only when a session is started. We ignore this case currently.
+bool WebRtcVideoEngine::SetDefaultCodec(const VideoCodec& codec) {
+  if (!RebuildCodecList(codec)) {
+    LOG(LS_WARNING) << "Failed to RebuildCodecList";
+    return false;
+  }
+
+  default_codec_format_ = VideoFormat(
+      video_codecs_[0].width,
+      video_codecs_[0].height,
+      VideoFormat::FpsToInterval(video_codecs_[0].framerate),
+      FOURCC_ANY);
+  return true;
+}
+
+WebRtcVideoMediaChannel* WebRtcVideoEngine::CreateChannel(
+    VoiceMediaChannel* voice_channel) {
+  WebRtcVideoMediaChannel* channel =
+      new WebRtcVideoMediaChannel(this, voice_channel);
+  if (!channel->Init()) {
+    delete channel;
+    channel = NULL;
+  }
+  return channel;
+}
+
+bool WebRtcVideoEngine::SetCaptureDevice(const Device* device) {
+  const bool owns_capturer = true;
+  if (!device) {
+    if (!SetCapturer(NULL, owns_capturer)) {
+      return false;
+    }
+    LOG(LS_INFO) << "Camera set to NULL";
+    return true;
+  }
+  // No-op if the device hasn't changed.
+  if ((video_capturer_ != NULL) && video_capturer_->GetId() == device->id) {
+    return true;
+  }
+  // Create a new capturer for the specified device.
+  VideoCapturer* capturer = CreateVideoCapturer(*device);
+  if (!capturer) {
+    LOG(LS_ERROR) << "Failed to create camera '" << device->name << "', id='"
+                  << device->id << "'";
+    return false;
+  }
+  if (!SetCapturer(capturer, owns_capturer)) {
+    return false;
+  }
+  LOG(LS_INFO) << "Camera set to '" << device->name << "', id='"
+               << device->id << "'";
+  return true;
+}
+
+bool WebRtcVideoEngine::SetCaptureModule(webrtc::VideoCaptureModule* vcm) {
+  const bool owns_capturer = true;
+  if (!vcm) {
+    if (!SetCapturer(NULL, owns_capturer)) {
+      return false;
+    }
+    LOG(LS_INFO) << "Camera set to NULL";
+    return true;
+  }
+  // Create a new capturer for the specified device.
+  WebRtcVideoCapturer* capturer = new WebRtcVideoCapturer;
+  if (!capturer->Init(vcm)) {
+    LOG(LS_ERROR) << "Failed to create camera from VCM";
+    delete capturer;
+    return false;
+  }
+  if (!SetCapturer(capturer, owns_capturer)) {
+    return false;
+  }
+  LOG(LS_INFO) << "Camera created with VCM";
+  CaptureResult ret = SetCapture(true);
+  if (ret != cricket::CR_SUCCESS && ret != cricket::CR_PENDING) {
+    LOG(LS_ERROR) << "Failed to start camera.";
+    return false;
+  }
+  return true;
+}
+
+bool WebRtcVideoEngine::SetVideoCapturer(VideoCapturer* capturer,
+                                         uint32 /*ssrc*/) {
+  const bool owns_capturer = false;
+  return SetCapturer(capturer, owns_capturer);
+}
+
+bool WebRtcVideoEngine::SetLocalRenderer(VideoRenderer* renderer) {
+  local_renderer_w_ = local_renderer_h_ = 0;
+  local_renderer_ = renderer;
+  return true;
+}
+
+CaptureResult WebRtcVideoEngine::SetCapture(bool capture) {
+  bool old_capture = capture_started_;
+  capture_started_ = capture;
+  CaptureResult res = UpdateCapturingState();
+  if (res != CR_SUCCESS && res != CR_PENDING) {
+    capture_started_ = old_capture;
+  }
+  return res;
+}
+
+VideoCapturer* WebRtcVideoEngine::CreateVideoCapturer(const Device& device) {
+  WebRtcVideoCapturer* capturer = new WebRtcVideoCapturer;
+  if (!capturer->Init(device)) {
+    delete capturer;
+    return NULL;
+  }
+  return capturer;
+}
+
+CaptureResult WebRtcVideoEngine::UpdateCapturingState() {
+  CaptureResult result = CR_SUCCESS;
+
+  bool capture = capture_started_;
+  if (!IsCapturing() && capture) {  // Start capturing.
+    if (video_capturer_ == NULL) {
+      return CR_NO_DEVICE;
+    }
+
+    VideoFormat capture_format;
+    if (!video_capturer_->GetBestCaptureFormat(default_codec_format_,
+                                               &capture_format)) {
+      LOG(LS_WARNING) << "Unsupported format:"
+                      << " width=" << default_codec_format_.width
+                      << " height=" << default_codec_format_.height
+                      << ". Supported formats are:";
+      const std::vector<VideoFormat>* formats =
+          video_capturer_->GetSupportedFormats();
+      if (formats) {
+        for (std::vector<VideoFormat>::const_iterator i = formats->begin();
+             i != formats->end(); ++i) {
+          const VideoFormat& format = *i;
+          LOG(LS_WARNING) << "  " << GetFourccName(format.fourcc) << ":"
+                          << format.width << "x" << format.height << "x"
+                          << format.framerate();
+        }
+      }
+      return CR_FAILURE;
+    }
+
+    // Start the video capturer.
+    result = video_capturer_->Start(capture_format);
+    if (CR_SUCCESS != result && CR_PENDING != result) {
+      LOG(LS_ERROR) << "Failed to start the video capturer";
+      return result;
+    }
+  } else if (IsCapturing() && !capture) {  // Stop capturing.
+    video_capturer_->Stop();
+  }
+
+  return result;
+}
+
+bool WebRtcVideoEngine::IsCapturing() const {
+  return (video_capturer_ != NULL) && video_capturer_->IsRunning();
+}
+
+void WebRtcVideoEngine::OnFrameCaptured(VideoCapturer* capturer,
+                                        const CapturedFrame* frame) {
+  // Force 16:10 for now. We'll be smarter with the capture refactor.
+  int cropped_height = frame->width * default_codec_format_.height
+      / default_codec_format_.width;
+  if (cropped_height > frame->height) {
+    // TODO: Once we support horizontal cropping, add cropped_width.
+    cropped_height = frame->height;
+  }
+
+  // This CapturedFrame* will already be in I420. In the future, when
+  // WebRtcVideoFrame has support for independent planes, we can just attach
+  // to it and update the pointers when cropping.
+  WebRtcVideoFrame i420_frame;
+  if (!i420_frame.Init(frame, frame->width, cropped_height)) {
+    LOG(LS_ERROR) << "Couldn't convert to I420! "
+                  << frame->width << " x " << cropped_height;
+    return;
+  }
+
+  // TODO: This is the trigger point for Tx video processing.
+  // Once the capturer refactoring is done, we will move this into the
+  // capturer...it's not there right now because that image is in not in the
+  // I420 color space.
+  // The clients that subscribe will obtain meta info from the frame.
+  // When this trigger is switched over to capturer, need to pass in the real
+  // ssrc.
+  {
+    talk_base::CritScope cs(&signal_media_critical_);
+    SignalMediaFrame(kDummyVideoSsrc, &i420_frame);
+  }
+
+  // Send I420 frame to the local renderer.
+  if (local_renderer_) {
+    if (local_renderer_w_ != static_cast<int>(i420_frame.GetWidth()) ||
+        local_renderer_h_ != static_cast<int>(i420_frame.GetHeight())) {
+      local_renderer_->SetSize(local_renderer_w_ = i420_frame.GetWidth(),
+                               local_renderer_h_ = i420_frame.GetHeight(), 0);
+    }
+    local_renderer_->RenderFrame(&i420_frame);
+  }
+
+  // Send I420 frame to the registered senders.
+  talk_base::CritScope cs(&channels_crit_);
+  for (VideoChannels::iterator it = channels_.begin();
+      it != channels_.end(); ++it) {
+    if ((*it)->sending()) (*it)->SendFrame(0, &i420_frame);
+  }
+}
+
+const std::vector<VideoCodec>& WebRtcVideoEngine::codecs() const {
+  return video_codecs_;
+}
+
+void WebRtcVideoEngine::SetLogging(int min_sev, const char* filter) {
+  // if min_sev == -1, we keep the current log level.
+  if (min_sev >= 0) {
+    log_level_ = min_sev;
+  }
+  ApplyLogging(filter);
+}
+
+int WebRtcVideoEngine::GetLastEngineError() {
+  return vie_wrapper_->error();
+}
+
+// Checks to see whether we comprehend and could receive a particular codec
+bool WebRtcVideoEngine::FindCodec(const VideoCodec& in) {
+  for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) {
+    const VideoFormat fmt(kVideoFormats[i]);
+    if ((in.width == 0 && in.height == 0) ||
+        (fmt.width == in.width && fmt.height == in.height)) {
+      for (int j = 0; j < ARRAY_SIZE(kVideoCodecPrefs); ++j) {
+        VideoCodec codec(kVideoCodecPrefs[j].payload_type,
+                         kVideoCodecPrefs[j].name, 0, 0, 0, 0);
+        if (codec.Matches(in)) {
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
+// Given the requested codec, returns true if we can send that codec type and
+// updates out with the best quality we could send for that codec. If current is
+// not empty, we constrain out so that its aspect ratio matches current's.
+bool WebRtcVideoEngine::CanSendCodec(const VideoCodec& requested,
+                                     const VideoCodec& current,
+                                     VideoCodec* out) {
+  if (!out) {
+    return false;
+  }
+
+  std::vector<VideoCodec>::const_iterator local_max;
+  for (local_max = video_codecs_.begin();
+       local_max < video_codecs_.end();
+       ++local_max) {
+    // First match codecs by payload type
+    if (!requested.Matches(local_max->id, local_max->name)) {
+      continue;
+    }
+
+    out->id = requested.id;
+    out->name = requested.name;
+    out->preference = requested.preference;
+    out->framerate = talk_base::_min(requested.framerate, local_max->framerate);
+    out->width = 0;
+    out->height = 0;
+
+    if (0 == requested.width && 0 == requested.height) {
+      // Special case with resolution 0. The channel should not send frames.
+      return true;
+    } else if (0 == requested.width || 0 == requested.height) {
+      // 0xn and nx0 are invalid resolutions.
+      return false;
+    }
+
+    // Pick the best quality that is within their and our bounds and has the
+    // correct aspect ratio.
+    for (int j = 0; j < ARRAY_SIZE(kVideoFormats); ++j) {
+      const VideoFormat format(kVideoFormats[j]);
+
+      // Skip any format that is larger than the local or remote maximums, or
+      // smaller than the current best match
+      if (format.width > requested.width || format.height > requested.height ||
+          format.width > local_max->width ||
+          (format.width < out->width && format.height < out->height)) {
+        continue;
+      }
+
+      bool better = false;
+
+      // Check any further constraints on this prospective format
+      if (!out->width || !out->height) {
+        // If we don't have any matches yet, this is the best so far.
+        better = true;
+      } else if (current.width && current.height) {
+        // current is set so format must match its ratio exactly.
+        better =
+            (format.width * current.height == format.height * current.width);
+      } else {
+        // Prefer closer aspect ratios i.e
+        // format.aspect - requested.aspect < out.aspect - requested.aspect
+        better = abs(format.width * requested.height * out->height -
+                     requested.width * format.height * out->height) <
+                 abs(out->width * format.height * requested.height -
+                     requested.width * format.height * out->height);
+      }
+
+      if (better) {
+        out->width = format.width;
+        out->height = format.height;
+      }
+    }
+    if (out->width > 0) {
       return true;
     }
   }
   return false;
 }
 
-void WebRtcVideoEngine::Print(const webrtc::TraceLevel level,
-                              const char* trace, const int length) {
-  talk_base::LoggingSeverity sev = talk_base::LS_VERBOSE;
-  if (level == webrtc::kTraceError || level == webrtc::kTraceCritical)
-    sev = talk_base::LS_ERROR;
-  else if (level == webrtc::kTraceWarning)
-    sev = talk_base::LS_WARNING;
-  else if (level == webrtc::kTraceStateInfo || level == webrtc::kTraceInfo)
-    sev = talk_base::LS_INFO;
-
-  if (sev >= log_level_) {
-    // Skip past boilerplate prefix text
-    if (length < 72) {
-      std::string msg(trace, length);
-      LOG(LS_ERROR) << "Malformed webrtc log message: ";
-      LOG_V(sev) << msg;
-    } else {
-      std::string msg(trace + 71, length - 72);
-      if (!ShouldIgnoreTrace(msg)) {
-        LOG_V(sev) << "WebRtc ViE:" << msg;
-      }
-    }
-  }
+void WebRtcVideoEngine::ConvertToCricketVideoCodec(
+    const webrtc::VideoCodec& in_codec, VideoCodec* out_codec) {
+  out_codec->id = in_codec.plType;
+  out_codec->name = in_codec.plName;
+  out_codec->width = in_codec.width;
+  out_codec->height = in_codec.height;
+  out_codec->framerate = in_codec.maxFramerate;
 }
 
-void WebRtcVideoEngine::ApplyLogging() {
+bool WebRtcVideoEngine::ConvertFromCricketVideoCodec(
+    const VideoCodec& in_codec, webrtc::VideoCodec* out_codec) {
+  bool found = false;
+  int ncodecs = vie_wrapper_->codec()->NumberOfCodecs();
+  for (int i = 0; i < ncodecs; ++i) {
+    if (vie_wrapper_->codec()->GetCodec(i, *out_codec) == 0 &&
+        _stricmp(in_codec.name.c_str(), out_codec->plName) == 0) {
+      found = true;
+      break;
+    }
+  }
+
+  if (!found) {
+    LOG(LS_ERROR) << "invalid codec type";
+    return false;
+  }
+
+  if (in_codec.id != 0)
+    out_codec->plType = in_codec.id;
+
+  if (in_codec.width != 0)
+    out_codec->width = in_codec.width;
+
+  if (in_codec.height != 0)
+    out_codec->height = in_codec.height;
+
+  if (in_codec.framerate != 0)
+    out_codec->maxFramerate = in_codec.framerate;
+
+  // Init the codec with the default bandwidth options.
+  out_codec->minBitrate = kMinVideoBitrate;
+  out_codec->startBitrate = kStartVideoBitrate;
+  out_codec->maxBitrate = kMaxVideoBitrate;
+
+  return true;
+}
+
+void WebRtcVideoEngine::RegisterChannel(WebRtcVideoMediaChannel *channel) {
+  talk_base::CritScope cs(&channels_crit_);
+  channels_.push_back(channel);
+}
+
+void WebRtcVideoEngine::UnregisterChannel(WebRtcVideoMediaChannel *channel) {
+  talk_base::CritScope cs(&channels_crit_);
+  channels_.erase(std::remove(channels_.begin(), channels_.end(), channel),
+                  channels_.end());
+}
+
+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;
+}
+
+// See https://sites.google.com/a/google.com/wavelet/
+//     Home/Magic-Flute--RTC-Engine-/Magic-Flute-Command-Line-Parameters
+// for all supported command line setttings.
+void WebRtcVideoEngine::ApplyLogging(const std::string& log_filter) {
   int filter = 0;
   switch (log_level_) {
     case talk_base::LS_VERBOSE: filter |= webrtc::kTraceAll;
@@ -264,6 +873,19 @@
     case talk_base::LS_ERROR: filter |=
         webrtc::kTraceError | webrtc::kTraceCritical;
   }
+  tracing_->SetTraceFilter(filter);
+
+  // Set WebRTC trace file.
+  std::vector<std::string> opts;
+  talk_base::tokenize(log_filter, ' ', '"', '"', &opts);
+  std::vector<std::string>::iterator tracefile =
+      std::find(opts.begin(), opts.end(), "tracefile");
+  if (tracefile != opts.end() && ++tracefile != opts.end()) {
+    // Write WebRTC debug output (at same loglevel) to file
+    if (tracing_->SetTraceFile(tracefile->c_str()) == -1) {
+      LOG_RTCERR1(SetTraceFile, *tracefile);
+    }
+  }
 }
 
 // Rebuilds the codec list to be only those that are less intensive
@@ -290,324 +912,101 @@
   return true;
 }
 
-void WebRtcVideoEngine::Terminate() {
-  LOG(LS_INFO) << "WebRtcVideoEngine::Terminate";
-  initialized_ = false;
-  SetCapture(false);
-  if (local_renderer_.get()) {
-    // If the renderer already set, stop it first
-    if (vie_wrapper_->render()->StopRender(capture_id_) != 0)
-      LOG_RTCERR1(StopRender, capture_id_);
-  }
-
-  if (vie_wrapper_->render()->DeRegisterVideoRenderModule(
-          *render_module_.get()) != 0)
-    LOG_RTCERR0(DeRegisterVideoRenderModule);
-
-  if ((vie_wrapper_->base()->DeregisterObserver()) != 0)
-    LOG_RTCERR0(DeregisterObserver);
-
-  if ((vie_wrapper_->base()->SetVoiceEngine(NULL)) != 0)
-    LOG_RTCERR0(SetVoiceEngine);
-
-  if (vie_wrapper_->engine()->SetTraceCallback(NULL) != 0)
-    LOG_RTCERR0(SetTraceCallback);
-}
-
-int WebRtcVideoEngine::GetCapabilities() {
-  return VIDEO_RECV | VIDEO_SEND;
-}
-
-bool WebRtcVideoEngine::SetOptions(int options) {
-  return true;
-}
-
-bool WebRtcVideoEngine::ReleaseCaptureDevice() {
-  if (capture_id_ != -1) {
-    // Stop capture
-    SetCapture(false);
-    // DisconnectCaptureDevice
-    WebRtcVideoMediaChannel* channel;
-    for (VideoChannels::const_iterator it = channels_.begin();
-        it != channels_.end(); ++it) {
-      ASSERT(*it != NULL);
-      channel = *it;
-      // Ignore the return value here as the channel may not have connected to
-      // the capturer yet.
-      vie_wrapper_->capture()->DisconnectCaptureDevice(
-          channel->video_channel());
-      channel->set_connected(false);
-    }
-    // ReleaseCaptureDevice
-    vie_wrapper_->capture()->ReleaseCaptureDevice(capture_id_);
-    capture_id_ = -1;
-  }
-
-  return true;
-}
-
-bool WebRtcVideoEngine::SetCaptureDevice(const Device* cam) {
-  ASSERT(cam != NULL);
-
-  ReleaseCaptureDevice();
-
-  webrtc::ViECapture* vie_capture = vie_wrapper_->capture();
-
-  // There's an external VCM
-  if (capture_module_) {
-    if (vie_capture->AllocateCaptureDevice(*capture_module_, capture_id_) != 0)
-      ASSERT(capture_id_ == -1);
-  } else if (!external_capture_) {
-    char device_name[256], device_id[256];
-    bool found = false;
-    for (int i = 0; i < vie_capture->NumberOfCaptureDevices(); ++i) {
-      if (vie_capture->GetCaptureDevice(i, device_name, ARRAY_SIZE(device_name),
-                                        device_id,
-                                        ARRAY_SIZE(device_id)) == 0) {
-        // TODO: We should only compare the device_id here,
-        // however the devicemanager and webrtc use different format for th v4l2
-        // device id. So here we also compare the device_name for now.
-        // For example "usb-0000:00:1d.7-6" vs "/dev/video0".
-        if (cam->name.compare(device_name) == 0 ||
-            cam->id.compare(device_id) == 0) {
-          LOG(INFO) << "Found video capture device: " << device_name;
-          found = true;
-          break;
-        }
-      }
-    }
-    if (!found) {
+bool WebRtcVideoEngine::SetCapturer(VideoCapturer* capturer,
+                                    bool own_capturer) {
+  if (capturer == NULL) {
+    // Stop capturing before clearing the capturer.
+    if (SetCapture(false) != CR_SUCCESS) {
+      LOG(LS_WARNING) << "Camera failed to stop";
       return false;
     }
-    if (vie_capture->AllocateCaptureDevice(device_id, strlen(device_id),
-                                           capture_id_) != 0) {
-      ASSERT(capture_id_ == -1);
-    }
+    ClearCapturer();
+    return true;
   }
-
-  if (capture_id_ != -1) {
-    // Connect to all the channels if there is any.
-    WebRtcVideoMediaChannel* channel;
-    for (VideoChannels::const_iterator it = channels_.begin();
-         it != channels_.end(); ++it) {
-      ASSERT(*it != NULL);
-      channel = *it;
-
-      // No channel should have been connected yet.
-      // In case of switching device, all channel connections should have been
-      // disconnected in ReleaseCaptureDevice() first.
-      ASSERT(!channel->connected());
-
-      if (vie_capture->ConnectCaptureDevice(capture_id_,
-                                            channel->video_channel()) == 0) {
-        channel->set_connected(true);
-      } else {
-        LOG(LS_WARNING) << "SetCaptureDevice failed to ConnectCaptureDevice.";
-      }
-    }
-    SetCapture(true);
-  }
-
-  return (capture_id_ != -1);
-}
-
-bool WebRtcVideoEngine::SetCaptureModule(webrtc::VideoCaptureModule* vcm) {
-  ReleaseCaptureDevice();
-  if (capture_module_) {
-    capture_module_->Release();
-    capture_module_ = NULL;
-  }
-
-  if (vcm) {
-    capture_module_ = vcm;
-    capture_module_->AddRef();
-    external_capture_ = true;
-  } else {
-    external_capture_ = false;
-  }
-
-  return true;
-}
-
-bool WebRtcVideoEngine::SetLocalRenderer(VideoRenderer* renderer) {
-  if (local_renderer_.get()) {
-    // If the renderer already set, stop and remove it first
-    if (vie_wrapper_->render()->StopRender(capture_id_) != 0) {
-      LOG_RTCERR1(StopRender, capture_id_);
-    }
-    if (vie_wrapper_->render()->RemoveRenderer(capture_id_) != 0) {
-      LOG_RTCERR1(RemoveRenderer, capture_id_);
-    }
-  }
-  local_renderer_.reset(new WebRtcRenderAdapter(renderer));
-
-  int ret;
-  ret = vie_wrapper_->render()->AddRenderer(capture_id_,
-                                            webrtc::kVideoI420,
-                                            local_renderer_.get());
-  if (ret != 0)
+  // Hook up signals and install the supplied capturer.
+  SignalCaptureResult.repeat(capturer->SignalStartResult);
+  capturer->SignalFrameCaptured.connect(this,
+      &WebRtcVideoEngine::OnFrameCaptured);
+  ClearCapturer();
+  video_capturer_ = capturer;
+  owns_capturer_ = own_capturer;
+  // Possibly restart the capturer if it is supposed to be running.
+  CaptureResult result = UpdateCapturingState();
+  if (result != CR_SUCCESS && result != CR_PENDING) {
+    LOG(LS_WARNING) << "Camera failed to restart";
     return false;
-  ret = vie_wrapper_->render()->StartRender(capture_id_);
-  return (ret == 0);
-}
-
-CaptureResult WebRtcVideoEngine::SetCapture(bool capture) {
-  if ((capture_started_ != capture) && (capture_id_ != -1)) {
-    int ret;
-    if (capture)
-      ret = vie_wrapper_->capture()->StartCapture(capture_id_);
-    else
-      ret = vie_wrapper_->capture()->StopCapture(capture_id_);
-    if (ret != 0)
-      return CR_NO_DEVICE;
-    capture_started_ = capture;
   }
-  return CR_SUCCESS;
-}
-
-const std::vector<VideoCodec>& WebRtcVideoEngine::codecs() const {
-  return video_codecs_;
-}
-
-void WebRtcVideoEngine::SetLogging(int min_sev, const char* filter) {
-  log_level_ = min_sev;
-  ApplyLogging();
-}
-
-int WebRtcVideoEngine::GetLastEngineError() {
-  return vie_wrapper_->error();
-}
-
-bool WebRtcVideoEngine::SetDefaultEncoderConfig(
-    const VideoEncoderConfig& config) {
-  default_encoder_config_ = config;
   return true;
 }
 
-WebRtcVideoMediaChannel* WebRtcVideoEngine::CreateChannel(
-    VoiceMediaChannel* voice_channel) {
-  WebRtcVideoMediaChannel* channel =
-      new WebRtcVideoMediaChannel(this, voice_channel);
-  if (channel) {
-    if (!channel->Init()) {
-      delete channel;
-      channel = NULL;
-    }
-  }
-  return channel;
+void WebRtcVideoEngine::PerformanceAlarm(const unsigned int cpu_load) {
+  LOG(LS_INFO) << "WebRtcVideoEngine::PerformanceAlarm";
 }
 
-// Checks to see whether we comprehend and could receive a particular codec
-bool WebRtcVideoEngine::FindCodec(const VideoCodec& in) {
-  for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) {
-    const VideoFormat& fmt = kVideoFormats[i];
-    if ((in.width == 0 && in.height == 0) ||
-        (fmt.width == in.width && fmt.height == in.height)) {
-      for (int j = 0; j < ARRAY_SIZE(kVideoCodecPrefs); ++j) {
-        VideoCodec codec(kVideoCodecPrefs[j].payload_type,
-                         kVideoCodecPrefs[j].name, 0, 0, 0, 0);
-        if (codec.Matches(in)) {
-          return true;
-        }
-      }
+// Ignore spammy trace messages, mostly from the stats API when we haven't
+// gotten RTCP info yet from the remote side.
+bool WebRtcVideoEngine::ShouldIgnoreTrace(const std::string& trace) {
+  static const char* const kTracesToIgnore[] = {
+    NULL
+  };
+  for (const char* const* p = kTracesToIgnore; *p; ++p) {
+    if (trace.find(*p) == 0) {
+      return true;
     }
   }
   return false;
 }
 
-// SetDefaultCodec may be called while the capturer is running. For example, a
-// test call is started in a page with QVGA default codec, and then a real call
-// is started in another page with VGA default codec. This is the corner case
-// and happens only when a session is started. We ignore this case currently.
-bool WebRtcVideoEngine::SetDefaultCodec(const VideoCodec& codec) {
-  if (!RebuildCodecList(codec)) {
-    LOG(LS_WARNING) << "Failed to RebuildCodecList";
-    return false;
-  }
-  return true;
+int WebRtcVideoEngine::GetNumOfChannels() {
+  talk_base::CritScope cs(&channels_crit_);
+  return channels_.size();
 }
 
-void WebRtcVideoEngine::ConvertToCricketVideoCodec(
-    const webrtc::VideoCodec& in_codec, VideoCodec& out_codec) {
-  out_codec.id = in_codec.plType;
-  out_codec.name = in_codec.plName;
-  out_codec.width = in_codec.width;
-  out_codec.height = in_codec.height;
-  out_codec.framerate = in_codec.maxFramerate;
-}
+void WebRtcVideoEngine::Print(const webrtc::TraceLevel level,
+                              const char* trace, const int length) {
+  talk_base::LoggingSeverity sev = talk_base::LS_VERBOSE;
+  if (level == webrtc::kTraceError || level == webrtc::kTraceCritical)
+    sev = talk_base::LS_ERROR;
+  else if (level == webrtc::kTraceWarning)
+    sev = talk_base::LS_WARNING;
+  else if (level == webrtc::kTraceStateInfo || level == webrtc::kTraceInfo)
+    sev = talk_base::LS_INFO;
 
-bool WebRtcVideoEngine::ConvertFromCricketVideoCodec(
-    const VideoCodec& in_codec, webrtc::VideoCodec& out_codec) {
-  bool found = false;
-  int ncodecs = vie_wrapper_->codec()->NumberOfCodecs();
-  for (int i = 0; i < ncodecs; ++i) {
-    if ((vie_wrapper_->codec()->GetCodec(i, out_codec) == 0) &&
-        (strncmp(out_codec.plName,
-                 in_codec.name.c_str(),
-                 webrtc::kPayloadNameSize - 1) == 0)) {
-      found = true;
-      break;
+  if (sev >= log_level_) {
+    // Skip past boilerplate prefix text
+    if (length < 72) {
+      std::string msg(trace, length);
+      LOG(LS_ERROR) << "Malformed webrtc log message: ";
+      LOG_V(sev) << msg;
+    } else {
+      std::string msg(trace + 71, length - 72);
+      if (!ShouldIgnoreTrace(msg) &&
+          (!voice_engine_ || !voice_engine_->ShouldIgnoreTrace(msg))) {
+        LOG_V(sev) << "WebRtc:" << msg;
+      }
     }
   }
+}
 
-  if (!found) {
-    LOG(LS_ERROR) << "invalid codec type";
-    return false;
-  }
-
-  if (in_codec.id != 0)
-    out_codec.plType = in_codec.id;
-
-  if (in_codec.width != 0)
-    out_codec.width = in_codec.width;
-
-  if (in_codec.height != 0)
-    out_codec.height = in_codec.height;
-
-  if (in_codec.framerate != 0)
-    out_codec.maxFramerate = in_codec.framerate;
-
-  out_codec.maxBitrate = kMaxVideoBitrate;
-  out_codec.startBitrate = kStartVideoBitrate;
-  out_codec.minBitrate = kStartVideoBitrate;
-
+bool WebRtcVideoEngine::RegisterProcessor(
+    VideoProcessor* video_processor) {
+  talk_base::CritScope cs(&signal_media_critical_);
+  SignalMediaFrame.connect(video_processor,
+                           &VideoProcessor::OnFrame);
+  return true;
+}
+bool WebRtcVideoEngine::UnregisterProcessor(
+    VideoProcessor* video_processor) {
+  talk_base::CritScope cs(&signal_media_critical_);
+  SignalMediaFrame.disconnect(video_processor);
   return true;
 }
 
-int WebRtcVideoEngine::GetLastVideoEngineError() {
-  return vie_wrapper_->base()->LastError();
-}
-
-void WebRtcVideoEngine::RegisterChannel(WebRtcVideoMediaChannel *channel) {
-  channels_.push_back(channel);
-}
-
-void WebRtcVideoEngine::UnregisterChannel(WebRtcVideoMediaChannel *channel) {
-  VideoChannels::iterator i = std::find(channels_.begin(),
-                                      channels_.end(),
-                                      channel);
-  if (i != channels_.end()) {
-    channels_.erase(i);
+void WebRtcVideoEngine::ClearCapturer() {
+  if (owns_capturer_) {
+    delete video_capturer_;
   }
-}
-
-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;
+  video_capturer_ = NULL;
 }
 
 // WebRtcVideoMediaChannel
@@ -617,296 +1016,608 @@
     : engine_(engine),
       voice_channel_(channel),
       vie_channel_(-1),
+      vie_capture_(-1),
+      external_capture_(NULL),
       sending_(false),
-      connected_(false),
       render_started_(false),
-      send_codec_(NULL) {
+      muted_(false),
+      local_ssrc_(0),
+      first_receive_ssrc_(0),
+      send_min_bitrate_(kMinVideoBitrate),
+      send_start_bitrate_(kStartVideoBitrate),
+      send_max_bitrate_(kMaxVideoBitrate),
+      local_stream_info_(new WebRtcLocalStreamInfo()),
+      channel_options_(0) {
   engine->RegisterChannel(this);
 }
 
 bool WebRtcVideoMediaChannel::Init() {
-  bool ret = true;
-  if (engine_->video_engine()->base()->CreateChannel(vie_channel_) != 0) {
+  if (engine_->vie()->base()->CreateChannel(vie_channel_) != 0) {
     LOG_RTCERR1(CreateChannel, vie_channel_);
     return false;
   }
+  if (!ConfigureChannel(vie_channel_)) {
+    engine_->vie()->base()->DeleteChannel(vie_channel_);
+    vie_channel_ = -1;
+    return false;
+  }
+
+  if (!ConfigureReceiving(vie_channel_, 0)) {
+    engine_->vie()->base()->DeleteChannel(vie_channel_);
+    vie_channel_ = -1;
+    return false;
+  }
 
   LOG(LS_INFO) << "WebRtcVideoMediaChannel::Init "
-               << "video_channel " << vie_channel_ << " created";
+               << "vie_channel " << vie_channel_ << " created";
 
-  // connect audio channel
-  if (voice_channel_) {
-    WebRtcVoiceMediaChannel* channel =
-        static_cast<WebRtcVoiceMediaChannel*> (voice_channel_);
-    if (engine_->video_engine()->base()->ConnectAudioChannel(
-        vie_channel_, channel->voe_channel()) != 0) {
-      LOG(LS_WARNING) << "ViE ConnectAudioChannel failed"
-                   << "A/V not synchronized";
-      // Don't set ret to false;
-    }
+  // Register external capture.
+  if (engine()->vie()->capture()->AllocateExternalCaptureDevice(
+      vie_capture_, external_capture_) != 0) {
+    LOG_RTCERR0(AllocateExternalCaptureDevice);
+    return false;
   }
 
-  // Register external transport
-  if (engine_->video_engine()->network()->RegisterSendTransport(
-      vie_channel_, *this) != 0) {
-    ret = false;
-  } else {
-    EnableRtcp();
-    EnablePLI();
+  // Connect external capture.
+  if (engine()->vie()->capture()->ConnectCaptureDevice(
+      vie_capture_, vie_channel_) != 0) {
+    LOG_RTCERR2(ConnectCaptureDevice, vie_capture_, vie_channel_);
+    return false;
   }
-  return ret;
+
+  // Register encoder observer for outgoing framerate and bitrate.
+  encoder_observer_.reset(new WebRtcEncoderObserver(vie_channel_));
+  if (engine()->vie()->codec()->RegisterEncoderObserver(
+      vie_channel_, *encoder_observer_) != 0) {
+    LOG_RTCERR1(RegisterEncoderObserver, encoder_observer_.get());
+    return false;
+  }
+
+  return true;
 }
 
 WebRtcVideoMediaChannel::~WebRtcVideoMediaChannel() {
-  // Stop and remote renderer
-  SetRender(false);
-  if (engine()->video_engine()->render()->RemoveRenderer(vie_channel_)
-      == -1) {
-    LOG_RTCERR1(RemoveRenderer, vie_channel_);
+  if (vie_channel_ != -1) {
+    // Stop sending.
+    SetSend(false);
+    if (engine()->vie()->codec()->DeregisterEncoderObserver(
+        vie_channel_) != 0) {
+      LOG_RTCERR1(DeregisterEncoderObserver, vie_channel_);
+    }
+
+    // Stop the renderer.
+    SetRender(false);
+
+    // Destroy the external capture interface.
+    if (vie_capture_ != -1) {
+      if (engine()->vie()->capture()->DisconnectCaptureDevice(
+          vie_channel_) != 0) {
+        LOG_RTCERR1(DisconnectCaptureDevice, vie_channel_);
+      }
+      if (engine()->vie()->capture()->ReleaseCaptureDevice(
+          vie_capture_) != 0) {
+        LOG_RTCERR1(ReleaseCaptureDevice, vie_capture_);
+      }
+    }
+
+    // Remove all receive streams and the default channel.
+    while (!mux_channels_.empty()) {
+      RemoveRecvStream(mux_channels_.begin()->first);
+    }
   }
 
-  // DeRegister external transport
-  if (engine()->video_engine()->network()->DeregisterSendTransport(
-      vie_channel_) == -1) {
-    LOG_RTCERR1(DeregisterSendTransport, vie_channel_);
-  }
-
-  // Unregister RtcChannel with the engine.
+  // Unregister the channel from the engine.
   engine()->UnregisterChannel(this);
-
-  // Delete VideoChannel
-  if (engine()->video_engine()->base()->DeleteChannel(vie_channel_) == -1) {
-    LOG_RTCERR1(DeleteChannel, vie_channel_);
-  }
 }
 
 bool WebRtcVideoMediaChannel::SetRecvCodecs(
     const std::vector<VideoCodec>& codecs) {
-  bool ret = true;
+  receive_codecs_.clear();
   for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
       iter != codecs.end(); ++iter) {
     if (engine()->FindCodec(*iter)) {
       webrtc::VideoCodec wcodec;
-      if (engine()->ConvertFromCricketVideoCodec(*iter, wcodec)) {
-        if (engine()->video_engine()->codec()->SetReceiveCodec(
-            vie_channel_,  wcodec) != 0) {
-          LOG_RTCERR2(SetReceiveCodec, vie_channel_, wcodec.plName);
-          ret = false;
-        }
+      if (engine()->ConvertFromCricketVideoCodec(*iter, &wcodec)) {
+        receive_codecs_.push_back(wcodec);
       }
     } else {
-      LOG(LS_INFO) << "Unknown codec" << iter->name;
-      ret = false;
+      LOG(LS_INFO) << "Unknown codec " << iter->name;
+      return false;
     }
   }
 
-  // make channel ready to receive packets
-  if (ret) {
-    if (engine()->video_engine()->base()->StartReceive(vie_channel_) != 0) {
-      LOG_RTCERR1(StartReceive, vie_channel_);
-      ret = false;
-    }
+  for (ChannelMap::iterator it = mux_channels_.begin();
+      it != mux_channels_.end(); ++it) {
+    if (!SetReceiveCodecs(it->second->channel_id()))
+      return false;
   }
-  return ret;
+  return true;
 }
 
 bool WebRtcVideoMediaChannel::SetSendCodecs(
     const std::vector<VideoCodec>& codecs) {
-  if (sending_) {
-    LOG(LS_ERROR) << "channel is alredy sending";
-    return false;
-  }
-
-  // match with local video codec list
+  // Match with local video codec list.
   std::vector<webrtc::VideoCodec> send_codecs;
+  int red_type = -1, fec_type = -1;
+  VideoCodec checked_codec;
+  VideoCodec current;  // defaults to 0x0
+  if (sending_) {
+    engine()->ConvertToCricketVideoCodec(*send_codec_, &current);
+  }
   for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
       iter != codecs.end(); ++iter) {
-    if (engine()->FindCodec(*iter)) {
+    if (_stricmp(iter->name.c_str(), kRedPayloadName) == 0) {
+      red_type = iter->id;
+    } else if (_stricmp(iter->name.c_str(), kFecPayloadName) == 0) {
+      fec_type = iter->id;
+    } else if (engine()->CanSendCodec(*iter, current, &checked_codec)) {
       webrtc::VideoCodec wcodec;
-      if (engine()->ConvertFromCricketVideoCodec(*iter, wcodec))
+      if (engine()->ConvertFromCricketVideoCodec(checked_codec, &wcodec)) {
         send_codecs.push_back(wcodec);
+      }
+    } else {
+      LOG(LS_WARNING) << "Unknown codec " << iter->name;
     }
   }
 
-  // if none matches, return with set
+  // Fail if we don't have a match.
   if (send_codecs.empty()) {
-    LOG(LS_ERROR) << "No matching codecs avilable";
+    LOG(LS_WARNING) << "No matching codecs avilable";
     return false;
   }
 
-  // select the first matched codec
-  const webrtc::VideoCodec& codec(send_codecs[0]);
-  send_codec_.reset(new webrtc::VideoCodec(codec));
-  if (engine()->video_engine()->codec()->SetSendCodec(
-      vie_channel_, codec) != 0) {
-    LOG_RTCERR2(SetSendCodec, vie_channel_, codec.plName);
+  // Configure video protection.
+  if (!SetNackFec(vie_channel_, red_type, fec_type)) {
     return false;
   }
+
+  // Select the first matched codec.
+  webrtc::VideoCodec& codec(send_codecs[0]);
+
+  // Set the default number of temporal layers for VP8.
+  if (webrtc::kVideoCodecVP8 == codec.codecType) {
+    codec.codecSpecific.VP8.numberOfTemporalLayers =
+        kDefaultNumberOfTemporalLayers;
+  }
+
+  if (!SetSendCodec(
+      codec, send_min_bitrate_, send_start_bitrate_, send_max_bitrate_)) {
+    return false;
+  }
+
+  LOG(LS_INFO) << "Selected video codec " << send_codec_->plName << "/"
+               << send_codec_->width << "x" << send_codec_->height << "x"
+               << static_cast<int>(send_codec_->maxFramerate);
+  if (webrtc::kVideoCodecVP8 == codec.codecType) {
+    LOG(LS_INFO) << "VP8 number of layers: "
+                 << static_cast<int>(
+                    send_codec_->codecSpecific.VP8.numberOfTemporalLayers);
+  }
   return true;
 }
 
+bool WebRtcVideoMediaChannel::SetSendStreamFormat(uint32 ssrc,
+                                                  const VideoFormat& format) {
+  // TODO: Handle multiple outgoing streams.
+  if (local_ssrc_ != ssrc) {
+    LOG(LS_ERROR) << "The specified ssrc " << ssrc
+                  << " differs from the send ssrc " << local_ssrc_;
+    return false;
+  }
+
+  if (send_codec_.get() == NULL) {
+    LOG(LS_ERROR) << "The send codec has not been set yet.";
+    return false;
+  }
+
+  webrtc::VideoCodec codec = *send_codec_.get();
+  codec.width = format.width;
+  codec.height = format.height;
+  codec.maxFramerate = VideoFormat::IntervalToFps(format.interval);
+
+  bool ret = SetSendCodec(
+      codec, send_min_bitrate_, send_start_bitrate_, send_max_bitrate_);
+  if (ret) {
+    LOG(LS_INFO) << "Update send codec resolution to "
+                 << codec.width << "x" << codec.height << "x"
+                 << static_cast<int>(codec.maxFramerate);
+  }
+  return ret;
+}
+
 bool WebRtcVideoMediaChannel::SetRender(bool render) {
-  if (render != render_started_) {
-    int ret;
-    if (render) {
-      ret = engine()->video_engine()->render()->StartRender(vie_channel_);
-    } else {
-      ret = engine()->video_engine()->render()->StopRender(vie_channel_);
-    }
-    if (ret != 0) {
-      return false;
-    }
-    render_started_ = render;
-  }
-  return true;
-}
-
-bool WebRtcVideoMediaChannel::SetSend(bool send) {
-  if (send == sending()) {
+  if (render == render_started_) {
     return true;  // no action required
   }
 
   bool ret = true;
-  if (send) {  // enable
-    if (engine()->video_engine()->base()->StartSend(vie_channel_) != 0) {
-      LOG_RTCERR1(StartSend, vie_channel_);
-      ret = false;
-    }
-
-    // If the channel has not been connected to the capturer yet,
-    // connect it now.
-    if (!connected()) {
-      if (engine()->video_engine()->capture()->ConnectCaptureDevice(
-              engine()->capture_id(), vie_channel_) != 0) {
-        LOG_RTCERR2(ConnectCaptureDevice, engine()->capture_id(), vie_channel_);
+  for (ChannelMap::iterator it = mux_channels_.begin();
+      it != mux_channels_.end(); ++it) {
+    if (render) {
+      if (engine()->vie()->render()->StartRender(
+          it->second->channel_id()) != 0) {
+        LOG_RTCERR1(StartRender, it->second->channel_id());
         ret = false;
-      } else {
-        set_connected(true);
       }
-    }
-  } else {  // disable
-    if (engine()->video_engine()->base()->StopSend(vie_channel_) != 0) {
-      LOG_RTCERR1(StopSend, vie_channel_);
-      ret = false;
+    } else {
+      if (engine()->vie()->render()->StopRender(
+          it->second->channel_id()) != 0) {
+        LOG_RTCERR1(StopRender, it->second->channel_id());
+        ret = false;
+      }
     }
   }
   if (ret) {
-    sending_ = send;
+    render_started_ = render;
   }
 
   return ret;
 }
 
-bool WebRtcVideoMediaChannel::AddStream(uint32 ssrc, uint32 voice_ssrc) {
-  return false;
-}
-
-bool WebRtcVideoMediaChannel::RemoveStream(uint32 ssrc) {
-  return false;
-}
-
-bool WebRtcVideoMediaChannel::SetRenderer(
-    uint32 ssrc, VideoRenderer* renderer) {
-  ASSERT(vie_channel_ != -1);
-  if (ssrc != 0)
+bool WebRtcVideoMediaChannel::SetSend(bool send) {
+  if (local_ssrc_ == 0 && send) {
+    LOG(LS_ERROR) << "No stream added";
     return false;
-  if (remote_renderer_.get()) {
-    // If the renderer already set, stop and remove it first
-    if (engine_->video_engine()->render()->StopRender(vie_channel_) != 0) {
-      LOG_RTCERR1(StopRender, vie_channel_);
+  }
+  if (send == sending()) {
+    return true;  // No action required.
+  }
+
+  if (send) {
+    // We've been asked to start sending.
+    // SetSendCodecs must have been called already.
+    if (!send_codec_.get()) {
+      return false;
     }
-    if (engine_->video_engine()->render()->RemoveRenderer(vie_channel_) != 0) {
-      LOG_RTCERR1(RemoveRenderer, vie_channel_);
+    // Start send now.
+    if (!StartSend()) {
+      return false;
+    }
+  } else {
+    // We've been asked to stop sending.
+    if (!StopSend()) {
+      return false;
     }
   }
-  remote_renderer_.reset(new WebRtcRenderAdapter(renderer));
+  sending_ = send;
+  
+  return true;
+}
 
-  if (engine_->video_engine()->render()->AddRenderer(vie_channel_,
-      webrtc::kVideoI420, remote_renderer_.get()) != 0) {
-    LOG_RTCERR3(AddRenderer, vie_channel_, webrtc::kVideoI420,
-                remote_renderer_.get());
-    remote_renderer_.reset();
+int WebRtcVideoMediaChannel::GetChannelNum(uint32 ssrc) {
+  ChannelMap::iterator it = mux_channels_.find(ssrc);
+  return (it != mux_channels_.end()) ? it->second->channel_id() : -1;
+}
+
+bool WebRtcVideoMediaChannel::AddSendStream(const StreamParams& sp) {
+  // TODO: Implement send media from multiple streams.
+  if (local_ssrc_ != 0) {
+    LOG(LS_ERROR) << "WebRtcVideoMediaChannel supports one sending channel";
+    return false;
+  }
+  if (sp.ssrcs.size() != 1) {
+    LOG(LS_ERROR) << "WebRtcVideoMediaChannel supports one sending SSRC per"
+                  << " stream";
     return false;
   }
 
-  if (engine_->video_engine()->render()->StartRender(vie_channel_) != 0) {
-    LOG_RTCERR1(StartRender, vie_channel_);
+  if (engine()->vie()->rtp()->SetRTCPCName(vie_channel_,
+                                           sp.cname.c_str()) != 0) {
+    LOG_RTCERR2(SetRTCPCName, vie_channel_, sp.cname.c_str());
     return false;
   }
 
+  local_ssrc_ = sp.first_ssrc();
+  // Set the SSRC on the receive channels and this send channel.
+  // Receive channels have to have the same SSRC in order to send receiver
+  // reports with this SSRC.
+  for (ChannelMap::const_iterator it = mux_channels_.begin();
+       it != mux_channels_.end(); ++it) {
+    WebRtcVideoChannelInfo* info = it->second;
+    int channel_id = info->channel_id();
+    if (engine()->vie()->rtp()->SetLocalSSRC(channel_id,
+                                             sp.first_ssrc()) != 0) {
+      LOG_RTCERR1(SetLocalSSRC, it->first);
+      return false;
+    }
+  }
+
+  if (sending_) {
+    return StartSend();
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::RemoveSendStream(uint32 ssrc) {
+  // TODO: Implement send media from multiple streams.
+  if (ssrc != local_ssrc_) {
+    return false;
+  }
+  if (sending_) {
+    StopSend();
+  }
+  local_ssrc_ = 0;
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::AddRecvStream(const StreamParams& sp) {
+  // TODO Remove this once BWE works properly across different send
+  // and receive channels.
+  // Reuse default channel for recv stream in 1:1 call.
+  if ((channel_options_ & OPT_CONFERENCE) == 0 && first_receive_ssrc_ == 0) {
+    LOG(LS_INFO) << "Recv stream " << sp.first_ssrc()
+                 << " reuse default channel #"
+                 << vie_channel_;
+    first_receive_ssrc_ = sp.first_ssrc();
+    return true;
+  }
+
+  if (mux_channels_.find(sp.first_ssrc()) != mux_channels_.end()) {
+    LOG(LS_ERROR) << "Stream already exists";
+    return false;
+  }
+
+  // TODO: Implement recv media from multiple SSRCs per stream.
+  if (sp.ssrcs.size() != 1) {
+    LOG(LS_ERROR) << "WebRtcVideoMediaChannel supports one receiving SSRC per"
+                  << " stream";
+    return false;
+  }
+
+  // Create a new channel for receiving video data.
+  // TODO: In order to support REMB we connect all receiving channels
+  // to our master send channel. This have to be done in a later cl when it have
+  // been properly implemented in webrtc.
+  int channel_id = -1;
+  if (engine_->vie()->base()->CreateChannel(channel_id) != 0) {
+    LOG_RTCERR1(CreateChannel, channel_id);
+    return false;
+  }
+
+  // Get the default renderer.
+  VideoRenderer* default_renderer = NULL;
+  if ((channel_options_ & OPT_CONFERENCE) != 0) {
+    if (mux_channels_.size() == 1 &&
+        mux_channels_.find(0) != mux_channels_.end()) {
+      GetRenderer(0, &default_renderer);
+    }
+  }
+
+  if (!ConfigureChannel(channel_id) ||
+      !ConfigureReceiving(channel_id, sp.first_ssrc())) {
+    engine_->vie()->base()->DeleteChannel(channel_id);
+    return false;
+  }
+
+  // The first recv stream reuses the default renderer (if a default renderer
+  // has been set).
+  if (default_renderer) {
+    SetRenderer(sp.first_ssrc(), default_renderer);
+  }
+
+  LOG(LS_INFO) << "New video stream " << sp.first_ssrc()
+               << " registered to VideoEngine channel #"
+               << channel_id;
+
   return true;
 }
 
+bool WebRtcVideoMediaChannel::RemoveRecvStream(uint32 ssrc) {
+  ChannelMap::iterator it = mux_channels_.find(ssrc);
+
+  if (it == mux_channels_.end()) {
+    // TODO: Remove this once BWE works properly across different send
+    // and receive channels.
+    // The default channel is reused for recv stream in 1:1 call.
+    if (first_receive_ssrc_ == ssrc) {
+      first_receive_ssrc_ = 0;
+      return true;
+    }
+    return false;
+  }
+  WebRtcVideoChannelInfo* info = it->second;
+  int channel_id = info->channel_id();
+  if (engine()->vie()->render()->RemoveRenderer(channel_id) != 0) {
+    LOG_RTCERR1(RemoveRenderer, channel_id);
+  }
+
+  if (engine()->vie()->network()->DeregisterSendTransport(channel_id) !=0) {
+    LOG_RTCERR1(DeRegisterSendTransport, channel_id);
+  }
+
+  if (engine()->vie()->codec()->DeregisterDecoderObserver(
+      channel_id) != 0) {
+    LOG_RTCERR1(DeregisterDecoderObserver, channel_id);
+  }
+
+  LOG(LS_INFO) << "Removing video stream " << ssrc
+               << " with VideoEngine channel #"
+               << channel_id;
+  if (engine()->vie()->base()->DeleteChannel(channel_id) == -1) {
+    LOG_RTCERR1(DeleteChannel, channel_id);
+    // Leak the WebRtcVideoChannelInfo owned by |it| but remove the channel from
+    // mux_channels_.
+    mux_channels_.erase(it);
+    return false;
+  }
+  // Delete the WebRtcVideoChannelInfo pointed to by it->second.
+  delete info;
+  mux_channels_.erase(it);
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::StartSend() {
+  if (engine()->vie()->base()->StartSend(vie_channel_) != 0) {
+    LOG_RTCERR1(StartSend, vie_channel_);
+    return false;
+  }
+
+  // TODO Change this once REMB supporting multiple sending channels.
+  // Send remb (2nd param) and use remb for BWE (3rd param).
+  engine_->vie()->rtp()->SetRembStatus(vie_channel_, true, true);
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::StopSend() {
+  if (engine()->vie()->base()->StopSend(vie_channel_) != 0) {
+    LOG_RTCERR1(StopSend, vie_channel_);
+    return false;
+  }
+
+  // TODO Change this once REMB supporting multiple sending channels.
+  // Don't send remb (2nd param) but use remb for BWE (3rd param).
+  engine_->vie()->rtp()->SetRembStatus(vie_channel_, false, true);
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetRenderer(uint32 ssrc,
+                                          VideoRenderer* renderer) {
+  if (mux_channels_.find(ssrc) == mux_channels_.end()) {
+    // TODO: Remove this once BWE works properly across different send
+    // and receive channels.
+    // The default channel is reused for recv stream in 1:1 call.
+    if (first_receive_ssrc_ == ssrc &&
+        mux_channels_.find(0) != mux_channels_.end()) {
+      LOG(LS_INFO) << "SetRenderer " << ssrc
+                   << " reuse default channel #"
+                   << vie_channel_;
+      mux_channels_[0]->SetRenderer(renderer);
+      return true;
+    }
+    return false;
+  }
+
+  mux_channels_[ssrc]->SetRenderer(renderer);
+  return true;
+}
+
 bool WebRtcVideoMediaChannel::GetStats(VideoMediaInfo* info) {
-  VideoSenderInfo sinfo;
-  memset(&sinfo, 0, sizeof(sinfo));
-
-  unsigned int ssrc;
-  if (engine_->video_engine()->rtp()->GetLocalSSRC(vie_channel_,
-                                                   ssrc) != 0) {
-    LOG_RTCERR2(GetLocalSSRC, vie_channel_, ssrc);
-    return false;
-  }
-  sinfo.ssrc = ssrc;
-
-  unsigned int cumulative_lost, extended_max, jitter;
-  int rtt_ms;
-  uint16 fraction_lost;
-
-  if (engine_->video_engine()->rtp()->GetReceivedRTCPStatistics(vie_channel_,
-          fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms) != 0) {
-    LOG_RTCERR6(GetReceivedRTCPStatistics, vie_channel_,
-        fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms);
-    return false;
-  }
-
-  sinfo.fraction_lost = fraction_lost;
-  sinfo.packets_lost = cumulative_lost;
-  sinfo.rtt_ms = rtt_ms;
-
+  // Get basic statistics.
   unsigned int bytes_sent, packets_sent, bytes_recv, packets_recv;
-  if (engine_->video_engine()->rtp()->GetRTPStatistics(vie_channel_,
+  unsigned int ssrc;
+  if (engine_->vie()->rtp()->GetRTPStatistics(vie_channel_,
           bytes_sent, packets_sent, bytes_recv, packets_recv) != 0) {
-    LOG_RTCERR5(GetRTPStatistics, vie_channel_,
-        bytes_sent, packets_sent, bytes_recv, packets_recv);
+    LOG_RTCERR1(GetRTPStatistics, vie_channel_);
     return false;
   }
-  sinfo.packets_sent = packets_sent;
-  sinfo.bytes_sent = bytes_sent;
-  sinfo.packets_lost = -1;
-  sinfo.packets_cached = -1;
 
-  info->senders.push_back(sinfo);
+  // Get sender statistics and build VideoSenderInfo.
+  if (engine_->vie()->rtp()->GetLocalSSRC(vie_channel_, ssrc) == 0) {
+    VideoSenderInfo sinfo;
+    sinfo.ssrc = ssrc;
+    sinfo.codec_name = send_codec_.get() ? send_codec_->plName : "";
+    sinfo.bytes_sent = bytes_sent;
+    sinfo.packets_sent = packets_sent;
+    sinfo.packets_cached = -1;
+    sinfo.packets_lost = -1;
+    sinfo.fraction_lost = -1;
+    sinfo.firs_rcvd = -1;
+    sinfo.nacks_rcvd = -1;
+    sinfo.rtt_ms = -1;
+    sinfo.frame_width = local_stream_info_->width();
+    sinfo.frame_height = local_stream_info_->height();
+    sinfo.framerate_input = local_stream_info_->framerate();
+    sinfo.framerate_sent = encoder_observer_->framerate();
+    sinfo.nominal_bitrate = encoder_observer_->bitrate();
+    sinfo.preferred_bitrate = send_max_bitrate_;
 
-  // build receiver info.
-  // reusing the above local variables
-  VideoReceiverInfo rinfo;
-  memset(&rinfo, 0, sizeof(rinfo));
-  if (engine_->video_engine()->rtp()->GetSentRTCPStatistics(vie_channel_,
-          fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms) != 0) {
-    LOG_RTCERR6(GetSentRTCPStatistics, vie_channel_,
-        fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms);
-    return false;
+    // Get received RTCP statistics for the sender, if available.
+    // It's not a fatal error if we can't, since RTCP may not have arrived yet.
+    uint16 r_fraction_lost;
+    unsigned int r_cumulative_lost;
+    unsigned int r_extended_max;
+    unsigned int r_jitter;
+    int r_rtt_ms;
+    if (engine_->vie()->rtp()->GetSentRTCPStatistics(vie_channel_,
+            r_fraction_lost, r_cumulative_lost, r_extended_max,
+            r_jitter, r_rtt_ms) == 0) {
+      // Convert Q8 to float.
+      sinfo.packets_lost = r_cumulative_lost;
+      sinfo.fraction_lost = static_cast<float>(r_fraction_lost) / (1 << 8);
+      sinfo.rtt_ms = r_rtt_ms;
+    }
+    info->senders.push_back(sinfo);
+  } else {
+    LOG_RTCERR1(GetLocalSSRC, vie_channel_);
   }
-  rinfo.bytes_rcvd = bytes_recv;
-  rinfo.packets_rcvd = packets_recv;
-  rinfo.fraction_lost = fraction_lost;
-  rinfo.packets_lost = cumulative_lost;
 
-  if (engine_->video_engine()->rtp()->GetRemoteSSRC(vie_channel_,
-                                                    ssrc) != 0) {
-    return false;
+  // Get the SSRC and stats for each receiver, based on our own calculations.
+  for (ChannelMap::const_iterator it = mux_channels_.begin();
+       it != mux_channels_.end(); ++it) {
+    // Don't report receive statistics from the default channel if we have
+    // specified receive channels.
+    if (it->first == 0 && mux_channels_.size() > 1)
+      continue;
+    WebRtcVideoChannelInfo* channel = it->second;
+
+    // Get receiver statistics and build VideoReceiverInfo, if we have data.
+    if (engine_->vie()->rtp()->GetRemoteSSRC(channel->channel_id(), ssrc) != 0)
+      continue;
+
+    if (engine_->vie()->rtp()->GetRTPStatistics(
+        channel->channel_id(), bytes_sent, packets_sent, bytes_recv,
+        packets_recv) != 0) {
+      LOG_RTCERR1(GetRTPStatistics, channel->channel_id());
+      return false;
+    }
+    VideoReceiverInfo rinfo;
+    rinfo.ssrc = ssrc;
+    rinfo.bytes_rcvd = bytes_recv;
+    rinfo.packets_rcvd = packets_recv;
+    rinfo.packets_lost = -1;
+    rinfo.packets_concealed = -1;
+    rinfo.fraction_lost = -1;  // from SentRTCP
+    rinfo.firs_sent = channel->decoder_observer()->firs_requested();
+    rinfo.nacks_sent = -1;
+    rinfo.frame_width = channel->render_adapter()->width();
+    rinfo.frame_height = channel->render_adapter()->height();
+    rinfo.framerate_rcvd = channel->decoder_observer()->framerate();
+    int fps = channel->render_adapter()->framerate();
+    rinfo.framerate_decoded = fps;
+    rinfo.framerate_output = fps;
+
+    // Get sent RTCP statistics.
+    uint16 s_fraction_lost;
+    unsigned int s_cumulative_lost;
+    unsigned int s_extended_max;
+    unsigned int s_jitter;
+    int s_rtt_ms;
+    if (engine_->vie()->rtp()->GetReceivedRTCPStatistics(channel->channel_id(),
+            s_fraction_lost, s_cumulative_lost, s_extended_max,
+            s_jitter, s_rtt_ms) == 0) {
+      // Convert Q8 to float.
+      rinfo.packets_lost = s_cumulative_lost;
+      rinfo.fraction_lost = static_cast<float>(s_fraction_lost) / (1 << 8);
+    }
+    info->receivers.push_back(rinfo);
   }
-  rinfo.ssrc = ssrc;
 
-  // Get codec for wxh
-  info->receivers.push_back(rinfo);
+  // Build BandwidthEstimationInfo.
+  // TODO: Fill in more BWE stats once we have them.
+  unsigned int total_bitrate_sent;
+  unsigned int video_bitrate_sent;
+  unsigned int fec_bitrate_sent;
+  unsigned int nack_bitrate_sent;
+  if (engine_->vie()->rtp()->GetBandwidthUsage(vie_channel_,
+      total_bitrate_sent, video_bitrate_sent,
+      fec_bitrate_sent, nack_bitrate_sent) == 0) {
+    BandwidthEstimationInfo bwe;
+    bwe.actual_enc_bitrate = video_bitrate_sent;
+    bwe.transmit_bitrate = total_bitrate_sent;
+    bwe.retransmit_bitrate = nack_bitrate_sent;
+    info->bw_estimations.push_back(bwe);
+  } else {
+    LOG_RTCERR1(GetBandwidthUsage, vie_channel_);
+  }
+
   return true;
 }
 
 bool WebRtcVideoMediaChannel::SendIntraFrame() {
   bool ret = true;
-  if (engine()->video_engine()->codec()->SendKeyFrame(vie_channel_) != 0) {
+  if (engine()->vie()->codec()->SendKeyFrame(vie_channel_) != 0) {
     LOG_RTCERR1(SendKeyFrame, vie_channel_);
     ret = false;
   }
@@ -921,59 +1632,86 @@
 }
 
 void WebRtcVideoMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
-  engine()->video_engine()->network()->ReceivedRTPPacket(vie_channel_,
-                                                         packet->data(),
-                                                         packet->length());
+  // Pick which channel to send this packet to. If this packet doesn't match
+  // any multiplexed streams, just send it to the default channel. Otherwise,
+  // send it to the specific decoder instance for that stream.
+  uint32 ssrc = 0;
+  if (!GetRtpSsrc(packet->data(), packet->length(), &ssrc))
+    return;
+  int which_channel = GetChannelNum(ssrc);
+  if (which_channel == -1) {
+    which_channel = video_channel();
+  }
+
+  engine()->vie()->network()->ReceivedRTPPacket(which_channel,
+                                                packet->data(),
+                                                packet->length());
 }
 
 void WebRtcVideoMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
-  engine_->video_engine()->network()->ReceivedRTCPPacket(vie_channel_,
-                                                         packet->data(),
-                                                         packet->length());
-}
+// Sending channels need all RTCP packets with feedback information.
+// Even sender reports can contain attached report blocks.
+// Receiving channels need sender reports in order to create
+// correct receiver reports.
 
-void WebRtcVideoMediaChannel::SetSendSsrc(uint32 id) {
-  if (!sending_) {
-    if (engine()->video_engine()->rtp()->SetLocalSSRC(vie_channel_,
-                                                      id) != 0) {
-      LOG_RTCERR1(SetLocalSSRC, vie_channel_);
+  uint32 ssrc = 0;
+  if (!GetRtcpSsrc(packet->data(), packet->length(), &ssrc)) {
+    LOG(LS_WARNING) << "Failed to parse SSRC from received RTCP packet";
+    return;
+  }
+  int type = 0;
+  if (!GetRtcpType(packet->data(), packet->length(), & type)) {
+    LOG(LS_WARNING) << "Failed to parse type from received RTCP packet";
+    return;
+  }
+
+  // If it is a sender report, find the channel that is listening.
+  if (type == kRtcpTypeSR) {
+    int which_channel = GetChannelNum(ssrc);
+    if (which_channel != -1 && which_channel != vie_channel_) {
+      engine_->vie()->network()->ReceivedRTCPPacket(which_channel,
+                                                    packet->data(),
+                                                    packet->length());
     }
-  } else {
-    LOG(LS_ERROR) << "Channel already in send state";
   }
-}
-
-bool WebRtcVideoMediaChannel::SetRtcpCName(const std::string& cname) {
-  if (engine()->video_engine()->rtp()->SetRTCPCName(vie_channel_,
-                                                    cname.c_str()) != 0) {
-    LOG_RTCERR2(SetRTCPCName, vie_channel_, cname.c_str());
-    return false;
-  }
-  return true;
+  // The sending channel receives all RTCP packets.
+  engine_->vie()->network()->ReceivedRTCPPacket(vie_channel_,
+                                                packet->data(),
+                                                packet->length());
 }
 
 bool WebRtcVideoMediaChannel::Mute(bool on) {
-  // stop send??
-  return false;
+  muted_ = on;
+  return true;
 }
 
 bool WebRtcVideoMediaChannel::SetSendBandwidth(bool autobw, int bps) {
   LOG(LS_INFO) << "RtcVideoMediaChanne::SetSendBandwidth";
 
   if (!send_codec_.get()) {
-    LOG(LS_INFO) << "The send codec has not been set up yet.";
+    LOG(LS_INFO) << "The send codec has not been set up yet";
     return true;
   }
 
-  if (!autobw) {
-    send_codec_->startBitrate = bps;
-    send_codec_->minBitrate = bps;
+  int min_bitrate;
+  int start_bitrate;
+  int max_bitrate;
+  if (autobw) {
+    // Use the default values for min bitrate.
+    min_bitrate = kMinVideoBitrate;
+    // Use the default value or the bps for the max
+    max_bitrate = (bps <= 0) ? send_max_bitrate_ : (bps / 1000);
+    // Maximum start bitrate can be kStartVideoBitrate.
+    start_bitrate = talk_base::_min(kStartVideoBitrate, max_bitrate);
+  } else {
+    // Use the default start or the bps as the target bitrate.
+    int target_bitrate = (bps <= 0) ? kStartVideoBitrate : (bps / 1000);
+    min_bitrate = target_bitrate;
+    start_bitrate = target_bitrate;
+    max_bitrate = target_bitrate;
   }
-  send_codec_->maxBitrate = bps;
 
-  if (engine()->video_engine()->codec()->SetSendCodec(vie_channel_,
-      *send_codec_.get()) != 0) {
-    LOG_RTCERR2(SetSendCodec, vie_channel_, send_codec_->plName);
+  if (!SetSendCodec(*send_codec_, min_bitrate, start_bitrate, max_bitrate)) {
     return false;
   }
 
@@ -981,6 +1719,33 @@
 }
 
 bool WebRtcVideoMediaChannel::SetOptions(int options) {
+  // Always accept options that are unchanged.
+  if (channel_options_ == options) {
+    return true;
+  }
+
+  // Reject new options if we're already sending.
+  if (sending()) {
+    return false;
+  }
+
+  // Save the options, to be interpreted where appropriate.
+  channel_options_ = options;
+
+  // Adjust send codec bitrate if needed.
+  int expected_bitrate = (0 != (channel_options_ & OPT_CONFERENCE)) ?
+      kConferenceModeMaxVideoBitrate : kMaxVideoBitrate;
+  if (NULL != send_codec_.get() && send_max_bitrate_ != expected_bitrate) {
+    // On success, SetSendCodec() will reset send_max_bitrate_ to
+    // expected_bitrate.
+    if (!SetSendCodec(*send_codec_,
+                      send_min_bitrate_,
+                      send_start_bitrate_,
+                      expected_bitrate)) {
+      return false;
+    }
+  }
+
   return true;
 }
 
@@ -997,18 +1762,309 @@
   }
 }
 
-void WebRtcVideoMediaChannel::EnableRtcp() {
-  engine()->video_engine()->rtp()->SetRTCPStatus(
-      vie_channel_, webrtc::kRtcpCompound_RFC4585);
+bool WebRtcVideoMediaChannel::GetRenderer(uint32 ssrc,
+                                          VideoRenderer** renderer) {
+  ChannelMap::const_iterator it = mux_channels_.find(ssrc);
+  if (it == mux_channels_.end()) {
+    if (first_receive_ssrc_ == ssrc &&
+        mux_channels_.find(0) != mux_channels_.end()) {
+      LOG(LS_INFO) << " GetRenderer " << ssrc
+                   << " reuse default renderer #"
+                   << vie_channel_;
+      *renderer = mux_channels_[0]->render_adapter()->renderer();
+      return true;
+    }
+    return false;
+  }
+
+  *renderer = it->second->render_adapter()->renderer();
+  return true;
 }
 
-void WebRtcVideoMediaChannel::EnablePLI() {
-  engine_->video_engine()->rtp()->SetKeyFrameRequestMethod(
-      vie_channel_, webrtc::kViEKeyFrameRequestPliRtcp);
+// TODO: Add unittests to test this function.
+bool WebRtcVideoMediaChannel::SendFrame(uint32 ssrc, const VideoFrame* frame) {
+  if (ssrc != 0 || !sending() || !external_capture_) {
+    return false;
+  }
+
+  // Update local stream statistics.
+  local_stream_info_->UpdateFrame(frame->GetWidth(), frame->GetHeight());
+
+  // Checks if we need to reset vie send codec.
+  if (!MaybeResetVieSendCodec(frame->GetWidth(), frame->GetHeight(), NULL)) {
+    LOG(LS_ERROR) << "MaybeResetVieSendCodec failed with "
+                  << frame->GetWidth() << "x" << frame->GetHeight();
+    return false;
+  }
+
+  // Blacken the frame if video is muted.
+  const VideoFrame* frame_out = frame;
+  talk_base::scoped_ptr<VideoFrame> black_frame;
+  if (muted_) {
+    black_frame.reset(frame->Copy());
+    black_frame->SetToBlack();
+    frame_out = black_frame.get();
+  }
+
+  webrtc::ViEVideoFrameI420 frame_i420;
+  // TODO: Update the webrtc::ViEVideoFrameI420
+  // to use const unsigned char*
+  frame_i420.y_plane = const_cast<unsigned char*>(frame_out->GetYPlane());
+  frame_i420.u_plane = const_cast<unsigned char*>(frame_out->GetUPlane());
+  frame_i420.v_plane = const_cast<unsigned char*>(frame_out->GetVPlane());
+  frame_i420.y_pitch = frame_out->GetYPitch();
+  frame_i420.u_pitch = frame_out->GetUPitch();
+  frame_i420.v_pitch = frame_out->GetVPitch();
+  frame_i420.width = frame_out->GetWidth();
+  frame_i420.height = frame_out->GetHeight();
+
+  // Convert from nanoseconds to milliseconds.
+  WebRtc_Word64 clocks = frame_out->GetTimeStamp() /
+      talk_base::kNumNanosecsPerMillisec;
+
+  return (external_capture_->IncomingFrameI420(frame_i420, clocks) == 0);
 }
 
-void WebRtcVideoMediaChannel::EnableTMMBR() {
-  engine_->video_engine()->rtp()->SetTMMBRStatus(vie_channel_, true);
+bool WebRtcVideoMediaChannel::ConfigureChannel(int channel_id) {
+  // Register external transport.
+  if (engine_->vie()->network()->RegisterSendTransport(
+      channel_id, *this) != 0) {
+    LOG_RTCERR1(RegisterSendTransport, channel_id);
+    return false;
+  }
+
+  // Set MTU.
+  if (engine_->vie()->network()->SetMTU(channel_id, kVideoMtu) != 0) {
+    LOG_RTCERR2(SetMTU, channel_id, kVideoMtu);
+    return false;
+  }
+  // Turn on RTCP and loss feedback reporting.
+  if (engine()->vie()->rtp()->SetRTCPStatus(
+      channel_id, webrtc::kRtcpCompound_RFC4585) != 0) {
+    LOG_RTCERR2(SetRTCPStatus, channel_id, webrtc::kRtcpCompound_RFC4585);
+    return false;
+  }
+  // Enable pli as key frame request method.
+  if (engine_->vie()->rtp()->SetKeyFrameRequestMethod(
+      channel_id, webrtc::kViEKeyFrameRequestPliRtcp) != 0) {
+    LOG_RTCERR2(SetKeyFrameRequestMethod,
+                channel_id, webrtc::kViEKeyFrameRequestPliRtcp);
+    return false;
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::ConfigureReceiving(int channel_id,
+                                                 uint32 remote_ssrc) {
+  // Connect the voice channel, if there is one.
+  // TODO: The A/V is synched by the receiving channel. So we need to
+  // know the SSRC of the remote audio channel in order to fetch the correct
+  // webrtc VoiceEngine channel. For now- only sync the default channel used
+  // in 1-1 calls.
+  if (remote_ssrc == 0 && voice_channel_) {
+    WebRtcVoiceMediaChannel* voice_channel =
+        static_cast<WebRtcVoiceMediaChannel*>(voice_channel_);
+    if (engine_->vie()->base()->ConnectAudioChannel(
+        vie_channel_, voice_channel->voe_channel()) != 0) {
+      LOG_RTCERR2(ConnectAudioChannel, channel_id,
+                  voice_channel->voe_channel());
+      LOG(LS_WARNING) << "A/V not synchronized";
+      // Not a fatal error.
+    }
+  }
+
+  talk_base::scoped_ptr<WebRtcVideoChannelInfo> channel_info(
+      new WebRtcVideoChannelInfo(channel_id));
+
+  // Install a render adapter.
+  if (engine_->vie()->render()->AddRenderer(channel_id,
+      webrtc::kVideoI420, channel_info->render_adapter()) != 0) {
+    LOG_RTCERR3(AddRenderer, channel_id, webrtc::kVideoI420,
+                channel_info->render_adapter());
+    return false;
+  }
+
+  // TODO Change this once REMB supporting multiple sending channels.
+  // Turn off remb sending (2nd param) and turn on remb reporting (3rd param)
+  // here.
+  // For sending channel, remb sending will be turned on after StartSending.
+  engine_->vie()->rtp()->SetRembStatus(channel_id, false, true);
+
+  if (remote_ssrc != 0) {
+    // Use the same SSRC as our default channel
+    // (so the RTCP reports are correct).
+    unsigned int send_ssrc = 0;
+    webrtc::ViERTP_RTCP* rtp = engine()->vie()->rtp();
+    if (rtp->GetLocalSSRC(vie_channel_, send_ssrc) == -1) {
+      LOG_RTCERR2(GetSendSSRC, channel_id, send_ssrc);
+      return false;
+    }
+    if (rtp->SetLocalSSRC(channel_id, send_ssrc) == -1) {
+      LOG_RTCERR2(SetSendSSRC, channel_id, send_ssrc);
+      return false;
+    }
+  }  // Else this is the the default channel and we don't change the SSRC.
+
+  // Disable color enhancement since it is a bit too aggressive.
+  if (engine()->vie()->image()->EnableColorEnhancement(channel_id,
+                                                       false) != 0) {
+    LOG_RTCERR1(EnableColorEnhancement, channel_id);
+    return false;
+  }
+
+  if (!SetReceiveCodecs(channel_id)) {
+    return false;
+  }
+
+  if (render_started_) {
+    if (engine_->vie()->render()->StartRender(channel_id) != 0) {
+      LOG_RTCERR1(StartRender, channel_id);
+      return false;
+    }
+  }
+
+  // Register decoder observer for incoming framerate and bitrate.
+  if (engine()->vie()->codec()->RegisterDecoderObserver(
+      channel_id, *channel_info->decoder_observer()) != 0) {
+    LOG_RTCERR1(RegisterDecoderObserver, channel_info->decoder_observer());
+    return false;
+  }
+
+  mux_channels_[remote_ssrc] = channel_info.release();
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetNackFec(int channel_id,
+                                         int red_payload_type,
+                                         int fec_payload_type) {
+  // Enable hybrid NACK/FEC if negotiated and not in a conference, use only NACK
+  // otherwise.
+  bool enable = (red_payload_type != -1 && fec_payload_type != -1 &&
+      !(channel_options_ & OPT_CONFERENCE));
+  if (enable) {
+    if (engine_->vie()->rtp()->SetHybridNACKFECStatus(
+        channel_id, enable, red_payload_type, fec_payload_type) != 0) {
+      LOG_RTCERR4(SetHybridNACKFECStatus,
+                  channel_id, enable, red_payload_type, fec_payload_type);
+      return false;
+    }
+    LOG(LS_INFO) << "Hybrid NACK/FEC enabled for channel " << channel_id;
+  } else {
+    if (engine_->vie()->rtp()->SetNACKStatus(channel_id, true) != 0) {
+      LOG_RTCERR1(SetNACKStatus, channel_id);
+      return false;
+    }
+    LOG(LS_INFO) << "NACK enabled for channel " << channel_id;
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetSendCodec(const webrtc::VideoCodec& codec,
+                                           int min_bitrate,
+                                           int start_bitrate,
+                                           int max_bitrate) {
+  // Make a copy of the codec
+  webrtc::VideoCodec target_codec = codec;
+  target_codec.startBitrate = start_bitrate;
+  target_codec.minBitrate = min_bitrate;
+  target_codec.maxBitrate = max_bitrate;
+
+  if (engine()->vie()->codec()->SetSendCodec(vie_channel_, target_codec) != 0) {
+    LOG_RTCERR2(SetSendCodec, vie_channel_, send_codec_->plName);
+    return false;
+  }
+
+  // Reset the send_codec_ only if SetSendCodec is success.
+  send_codec_.reset(new webrtc::VideoCodec(target_codec));
+  send_min_bitrate_ = min_bitrate;
+  send_start_bitrate_ = start_bitrate;
+  send_max_bitrate_ = max_bitrate;
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetReceiveCodecs(int channel_id) {
+  int red_type = -1;
+  int fec_type = -1;
+  for (std::vector<webrtc::VideoCodec>::iterator it = receive_codecs_.begin();
+       it != receive_codecs_.end(); ++it) {
+    if (it->codecType == webrtc::kVideoCodecRED) {
+      red_type = it->plType;
+    } else if (it->codecType == webrtc::kVideoCodecULPFEC) {
+      fec_type = it->plType;
+    }
+    if (engine()->vie()->codec()->SetReceiveCodec(channel_id, *it) != 0) {
+      LOG_RTCERR2(SetReceiveCodec, channel_id, it->plName);
+      return false;
+    }
+  }
+
+  // Enable video protection. For a sending channel, this will be taken care of
+  // in SetSendCodecs.
+  if (channel_id != vie_channel_) {
+    if (!SetNackFec(channel_id, red_type, fec_type)) {
+      return false;
+    }
+  }
+
+  // Start receiving packets if at least one receive codec has been set.
+  if (!receive_codecs_.empty()) {
+    if (engine()->vie()->base()->StartReceive(channel_id) != 0) {
+      LOG_RTCERR1(StartReceive, channel_id);
+      return false;
+    }
+  }
+  return true;
+}
+
+// If the new frame size is different from the send codec size we set on vie,
+// we need to reset the send codec on vie.
+// The new send codec size should not exceed send_codec_ which is controlled
+// only by the 'jec' logic.
+bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec(int new_width,
+                                                     int new_height,
+                                                     bool* reset) {
+  if (reset) {
+    *reset = false;
+  }
+
+  if (NULL == send_codec_.get()) {
+    return false;
+  }
+
+  // Vie send codec size should not exceed send_codec_.
+  int target_width = new_width;
+  int target_height = new_height;
+  if (new_width > send_codec_->width || new_height > send_codec_->height) {
+    target_width = send_codec_->width;
+    target_height = send_codec_->height;
+  }
+
+  // Get current vie codec.
+  webrtc::VideoCodec vie_codec;
+  if (engine()->vie()->codec()->GetSendCodec(vie_channel_, vie_codec) != 0) {
+    LOG_RTCERR1(GetSendCodec, vie_channel_);
+    return false;
+  }
+
+  // Only reset send codec when there is a size change.
+  if (target_width != vie_codec.width || target_height != vie_codec.height) {
+    // Set the new codec on vie.
+    vie_codec.width = target_width;
+    vie_codec.height = target_height;
+    if (engine()->vie()->codec()->SetSendCodec(vie_channel_, vie_codec) != 0) {
+      LOG_RTCERR1(SetSendCodec, vie_channel_);
+      return false;
+    }
+    if (reset) {
+      *reset = true;
+    }
+    LOG(LS_INFO) << "Reset vie send codec to: "
+                 << vie_codec.width << "x" << vie_codec.height;
+  }
+
+  return true;
 }
 
 int WebRtcVideoMediaChannel::SendPacket(int channel, const void* data,
@@ -1021,8 +2077,8 @@
 }
 
 int WebRtcVideoMediaChannel::SendRTCPPacket(int channel,
-                                         const void* data,
-                                         int len) {
+                                            const void* data,
+                                            int len) {
   if (!network_interface_) {
     return -1;
   }
@@ -1033,4 +2089,3 @@
 }  // namespace cricket
 
 #endif  // HAVE_WEBRTC_VIDEO
-
diff --git a/talk/session/phone/webrtcvideoengine.h b/talk/session/phone/webrtcvideoengine.h
index ffb6e7d..8eb56da 100644
--- a/talk/session/phone/webrtcvideoengine.h
+++ b/talk/session/phone/webrtcvideoengine.h
@@ -28,6 +28,7 @@
 #ifndef TALK_SESSION_PHONE_WEBRTCVIDEOENGINE_H_
 #define TALK_SESSION_PHONE_WEBRTCVIDEOENGINE_H_
 
+#include <map>
 #include <vector>
 
 #include "talk/base/scoped_ptr.h"
@@ -36,7 +37,7 @@
 #include "talk/session/phone/channel.h"
 #include "talk/session/phone/webrtccommon.h"
 #ifdef WEBRTC_RELATIVE_PATH
-#include "video_engine/main/interface/vie_base.h"
+#include "video_engine/include/vie_base.h"
 #else
 #include "third_party/webrtc/files/include/vie_base.h"
 #endif  // WEBRTC_RELATIVE_PATH
@@ -44,72 +45,104 @@
 namespace webrtc {
 class VideoCaptureModule;
 class VideoRender;
+class ViEExternalCapture;
 }
 
 namespace cricket {
+struct CapturedFrame;
+class WebRtcVideoChannelInfo;
 struct Device;
+class WebRtcLocalStreamInfo;
 class VideoCapturer;
+class VideoFrame;
+class VideoProcessor;
 class VideoRenderer;
+class ViETraceWrapper;
 class ViEWrapper;
 class VoiceMediaChannel;
 class WebRtcRenderAdapter;
 class WebRtcVideoMediaChannel;
 class WebRtcVoiceEngine;
+class WebRtcDecoderObserver;
+class WebRtcEncoderObserver;
 
-class WebRtcVideoEngine : public webrtc::ViEBaseObserver,
+class WebRtcVideoEngine : public sigslot::has_slots<>,
+                          public webrtc::ViEBaseObserver,
                           public webrtc::TraceCallback {
  public:
   // Creates the WebRtcVideoEngine with internal VideoCaptureModule.
   WebRtcVideoEngine();
   // For testing purposes. Allows the WebRtcVoiceEngine and
   // ViEWrapper to be mocks.
-  WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine, ViEWrapper* vie_wrapper);
+  // TODO: Remove the 2-arg ctor once fake tracing is implemented.
+  WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+                    ViEWrapper* vie_wrapper);
+  WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+                    ViEWrapper* vie_wrapper,
+                    ViETraceWrapper* tracing);
   ~WebRtcVideoEngine();
 
+  // Basic video engine implementation.
   bool Init();
   void Terminate();
 
-  WebRtcVideoMediaChannel* CreateChannel(
-      VoiceMediaChannel* voice_channel);
-  bool FindCodec(const VideoCodec& in);
-  bool SetDefaultEncoderConfig(const VideoEncoderConfig& config);
-
-  void RegisterChannel(WebRtcVideoMediaChannel* channel);
-  void UnregisterChannel(WebRtcVideoMediaChannel* channel);
-
-  ViEWrapper* video_engine() { return vie_wrapper_.get(); }
-  int GetLastVideoEngineError();
   int GetCapabilities();
   bool SetOptions(int options);
-  bool SetCaptureDevice(const Device* device);
-  bool SetCaptureModule(webrtc::VideoCaptureModule* vcm);
-  int capture_id() const { return capture_id_; }
-  bool SetLocalRenderer(VideoRenderer* renderer);
-  CaptureResult SetCapture(bool capture);
+  bool SetDefaultEncoderConfig(const VideoEncoderConfig& config);
+
+  WebRtcVideoMediaChannel* CreateChannel(VoiceMediaChannel* voice_channel);
+
   const std::vector<VideoCodec>& codecs() const;
   void SetLogging(int min_sev, const char* filter);
 
-  int GetLastEngineError();
+  // Capture-related stuff. Will be removed with capture refactor.
+  bool SetCaptureDevice(const Device* device);
+  bool SetCaptureModule(webrtc::VideoCaptureModule* vcm);
+  // If capturer is NULL, unregisters the capturer and stops capturing.
+  // Otherwise sets the capturer and starts capturing.
+  bool SetVideoCapturer(VideoCapturer* capturer, uint32 /*ssrc*/);
+  bool SetLocalRenderer(VideoRenderer* renderer);
+  CaptureResult SetCapture(bool capture);
+  sigslot::repeater2<VideoCapturer*, CaptureResult> SignalCaptureResult;
+  virtual VideoCapturer* CreateVideoCapturer(const Device& device);
+  CaptureResult UpdateCapturingState();
+  bool IsCapturing() const;
+  void OnFrameCaptured(VideoCapturer* capturer, const CapturedFrame* frame);
 
   // 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_;
+  bool RegisterProcessor(VideoProcessor* video_processor);
+  bool UnregisterProcessor(VideoProcessor* video_processor);
+
+  // Functions called by WebRtcVideoMediaChannel.
+  ViEWrapper* vie() { return vie_wrapper_.get(); }
+  const VideoFormat& default_codec_format() const {
+    return default_codec_format_;
   }
-
+  int GetLastEngineError();
+  bool FindCodec(const VideoCodec& in);
+  bool CanSendCodec(const VideoCodec& in, const VideoCodec& current,
+                    VideoCodec* out);
+  void RegisterChannel(WebRtcVideoMediaChannel* channel);
+  void UnregisterChannel(WebRtcVideoMediaChannel* channel);
   void ConvertToCricketVideoCodec(const webrtc::VideoCodec& in_codec,
-                                  VideoCodec& out_codec);
-
+                                  VideoCodec*  out_codec);
   bool ConvertFromCricketVideoCodec(const VideoCodec& in_codec,
-                                    webrtc::VideoCodec& out_codec);
+                                    webrtc::VideoCodec* out_codec);
+  // Check whether the supplied trace should be ignored.
+  bool ShouldIgnoreTrace(const std::string& trace);
+  int GetNumOfChannels();
 
-  sigslot::signal2<VideoCapturer*, CaptureResult> SignalCaptureResult;
+ protected:
+  // When a video processor registers with the engine.
+  // SignalMediaFrame will be invoked for every video frame.
+  sigslot::signal2<uint32, VideoFrame*> SignalMediaFrame;
 
  private:
+  typedef std::vector<WebRtcVideoMediaChannel*> VideoChannels;
   struct VideoCodecPref {
     const char* name;
     int payload_type;
@@ -117,35 +150,48 @@
   };
 
   static const VideoCodecPref kVideoCodecPrefs[];
-  static const VideoFormat kVideoFormats[];
-  static const VideoFormat kDefaultVideoFormat;
+  static const VideoFormatPod kVideoFormats[];
+  static const VideoFormatPod kDefaultVideoFormat;
 
-  void Construct();
+  void Construct(ViEWrapper* vie_wrapper,
+                 ViETraceWrapper* tracing,
+                 WebRtcVoiceEngine* voice_engine);
   bool SetDefaultCodec(const VideoCodec& codec);
   bool RebuildCodecList(const VideoCodec& max_codec);
-
-  void ApplyLogging();
+  void ApplyLogging(const std::string& log_filter);
   bool InitVideoEngine();
-  void PerformanceAlarm(const unsigned int cpu_load);
-  bool ReleaseCaptureDevice();
+  bool SetCapturer(VideoCapturer* capturer, bool own_capturer);
+
+  // webrtc::ViEBaseObserver implementation.
+  virtual void PerformanceAlarm(const unsigned int cpu_load);
+  // webrtc::TraceCallback implementation.
   virtual void Print(const webrtc::TraceLevel level, const char* trace_string,
                      const int length);
 
-  typedef std::vector<WebRtcVideoMediaChannel*> VideoChannels;
+  void ClearCapturer();
 
   talk_base::scoped_ptr<ViEWrapper> vie_wrapper_;
-  webrtc::VideoCaptureModule* capture_module_;
-  bool external_capture_;
-  int capture_id_;
-  talk_base::scoped_ptr<webrtc::VideoRender> render_module_;
+  bool vie_wrapper_base_initialized_;
+  talk_base::scoped_ptr<ViETraceWrapper> tracing_;
   WebRtcVoiceEngine* voice_engine_;
-  std::vector<VideoCodec> video_codecs_;
-  VideoChannels channels_;
   int log_level_;
-  VideoEncoderConfig default_encoder_config_;
-  bool capture_started_;
-  talk_base::scoped_ptr<WebRtcRenderAdapter> local_renderer_;
+  talk_base::scoped_ptr<webrtc::VideoRender> render_module_;
+  std::vector<VideoCodec> video_codecs_;
+  VideoFormat default_codec_format_;
   bool initialized_;
+  talk_base::CriticalSection channels_crit_;
+  VideoChannels channels_;
+
+  bool owns_capturer_;
+  VideoCapturer* video_capturer_;
+  bool capture_started_;
+  int local_renderer_w_;
+  int local_renderer_h_;
+  VideoRenderer* local_renderer_;
+
+  // Critical section to protect the media processor register/unregister
+  // while processing a frame
+  talk_base::CriticalSection signal_media_critical_;
 };
 
 class WebRtcVideoMediaChannel : public VideoMediaChannel,
@@ -154,23 +200,37 @@
   WebRtcVideoMediaChannel(
       WebRtcVideoEngine* engine, VoiceMediaChannel* voice_channel);
   ~WebRtcVideoMediaChannel();
-
   bool Init();
+
+  WebRtcVideoEngine* engine() { return engine_; }
+  VoiceMediaChannel* voice_channel() { return voice_channel_; }
+  int video_channel() const { return vie_channel_; }
+  bool sending() const { return sending_; }
+
+  // VideoMediaChannel implementation
   virtual bool SetRecvCodecs(const std::vector<VideoCodec> &codecs);
   virtual bool SetSendCodecs(const std::vector<VideoCodec> &codecs);
+  virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format);
   virtual bool SetRender(bool render);
   virtual bool SetSend(bool send);
-  virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc);
-  virtual bool RemoveStream(uint32 ssrc);
+
+  virtual bool AddSendStream(const StreamParams& sp);
+  virtual bool RemoveSendStream(uint32 ssrc);
+  virtual bool AddRecvStream(const StreamParams& sp);
+  virtual bool RemoveRecvStream(uint32 ssrc);
   virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
   virtual bool GetStats(VideoMediaInfo* info);
+  virtual bool AddScreencast(uint32 ssrc, const ScreencastId& id) {
+    return false;
+  }
+  virtual bool RemoveScreencast(uint32 ssrc) {
+    return false;
+  }
   virtual bool SendIntraFrame();
   virtual bool RequestIntraFrame();
 
   virtual void OnPacketReceived(talk_base::Buffer* packet);
   virtual void OnRtcpReceived(talk_base::Buffer* packet);
-  virtual void SetSendSsrc(uint32 id);
-  virtual bool SetRtcpCName(const std::string& cname);
   virtual bool Mute(bool on);
   virtual bool SetRecvRtpHeaderExtensions(
       const std::vector<RtpHeaderExtension>& extensions) {
@@ -184,12 +244,17 @@
   virtual bool SetOptions(int options);
   virtual void SetInterface(NetworkInterface* iface);
 
-  WebRtcVideoEngine* engine() const { return engine_; }
-  VoiceMediaChannel* voice_channel() const { return voice_channel_; }
-  int video_channel() const { return vie_channel_; }
-  bool sending() const { return sending_; }
-  void set_connected(bool connected) { connected_ = connected; }
-  bool connected() const { return connected_; }
+  // Public functions for use by tests and other specialized code.
+  uint32 send_ssrc() const { return 0; }
+  bool GetRenderer(uint32 ssrc, VideoRenderer** renderer);
+  bool SendFrame(uint32 ssrc, const VideoFrame* frame);
+
+  // Thunk functions for use with HybridVideoEngine
+  void OnLocalFrame(VideoCapturer* capturer, const VideoFrame* frame) {
+    SendFrame(0, frame);
+  }
+  void OnLocalFrameFormat(VideoCapturer* capturer, const VideoFormat* format) {
+  }
 
  protected:
   int GetLastEngineError() { return engine()->GetLastEngineError(); }
@@ -197,20 +262,55 @@
   virtual int SendRTCPPacket(int channel, const void* data, int len);
 
  private:
-  void EnableRtcp();
-  void EnablePLI();
-  void EnableTMMBR();
+  typedef std::map<uint32, WebRtcVideoChannelInfo*> ChannelMap;
+
+  // Creates and initializes a WebRtc video channel.
+  bool ConfigureChannel(int channel_id);
+  bool ConfigureReceiving(int channel_id, uint32 remote_ssrc);
+  bool SetNackFec(int channel_id, int red_payload_type, int fec_payload_type);
+  bool SetSendCodec(const webrtc::VideoCodec& codec,
+                    int min_bitrate,
+                    int start_bitrate,
+                    int max_bitrate);
+  // Prepares the channel with channel id |channel_id| to receive all codecs in
+  // |receive_codecs_| and start receive packets.
+  bool SetReceiveCodecs(int channel_id);
+  // Returns the channel number that receives the stream with SSRC |ssrc|.
+  int GetChannelNum(uint32 ssrc);
+  // Given captured video frame size, checks if we need to reset vie send codec.
+  // |reset| is set to whether resetting has happened on vie or not.
+  // Returns false on error.
+  bool MaybeResetVieSendCodec(int new_width, int new_height, bool* reset);
+  // Call Webrtc function to start sending media on |vie_channel_|.
+  // Does not affect |sending_|.
+  bool StartSend();
+  // Call Webrtc function to stop sending media on |vie_channel_|.
+  // Does not affect |sending_|.
+  bool StopSend();
 
   WebRtcVideoEngine* engine_;
   VoiceMediaChannel* voice_channel_;
   int vie_channel_;
+  int vie_capture_;
+  webrtc::ViEExternalCapture* external_capture_;
   bool sending_;
-  // connected to the capture device or not.
-  bool connected_;
   bool render_started_;
+  bool muted_;  // Flag to tell if we need to mute video.
+  // Our local SSRC. Currently only one send stream is supported.
+  uint32 local_ssrc_;
+  uint32 first_receive_ssrc_;
+  int send_min_bitrate_;
+  int send_start_bitrate_;
+  int send_max_bitrate_;
   talk_base::scoped_ptr<webrtc::VideoCodec> send_codec_;
-  talk_base::scoped_ptr<WebRtcRenderAdapter> remote_renderer_;
+  std::vector<webrtc::VideoCodec> receive_codecs_;
+  talk_base::scoped_ptr<WebRtcEncoderObserver> encoder_observer_;
+  talk_base::scoped_ptr<WebRtcLocalStreamInfo> local_stream_info_;
+  int channel_options_;
+
+  ChannelMap mux_channels_;  // Contains all receive channels.
 };
+
 }  // namespace cricket
 
 #endif  // TALK_SESSION_PHONE_WEBRTCVIDEOENGINE_H_
diff --git a/talk/session/phone/webrtcvideoengine_unittest.cc b/talk/session/phone/webrtcvideoengine_unittest.cc
index ee42729..b0bf813 100644
--- a/talk/session/phone/webrtcvideoengine_unittest.cc
+++ b/talk/session/phone/webrtcvideoengine_unittest.cc
@@ -1,93 +1,77 @@
-// Copyright 2008 Google Inc. All Rights Reserved.
-//
-// Author: Ronghua Wu (ronghuawu@google.com)
-//         Zhurun Zhang (zhurunz@google.com)
+/*
+ * 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/byteorder.h"
 #include "talk/base/gunit.h"
-#include "talk/session/phone/channel.h"
-#include "talk/session/phone/fakemediaengine.h"
-#include "talk/session/phone/fakertp.h"
-#include "talk/session/phone/fakesession.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/session/phone/fakemediaprocessor.h"
+#include "talk/session/phone/fakewebrtcvideocapturemodule.h"
 #include "talk/session/phone/fakewebrtcvideoengine.h"
 #include "talk/session/phone/fakewebrtcvoiceengine.h"
+#include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/videoengine_unittest.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
 #include "talk/session/phone/webrtcvideoengine.h"
+#include "talk/session/phone/webrtcvideoframe.h"
 #include "talk/session/phone/webrtcvoiceengine.h"
 
 // Tests for the WebRtcVideoEngine/VideoChannel code.
 
-static const cricket::VideoCodec kVP8Codec(104, "VP8", 320, 200, 30, 0);
+static const cricket::VideoCodec kVP8Codec(100, "VP8", 640, 400, 30, 0);
+static const cricket::VideoCodec kRedCodec(101, "red", 0, 0, 0, 0);
+static const cricket::VideoCodec kUlpFecCodec(102, "ulpfec", 0, 0, 0, 0);
 static const cricket::VideoCodec* const kVideoCodecs[] = {
     &kVP8Codec,
+    &kRedCodec,
+    &kUlpFecCodec
 };
 
+static const unsigned int kMinBandwidthKbps = 100;
+static const unsigned int kStartBandwidthKbps = 300;
+static const unsigned int kMaxBandwidthKbps = 2000;
+static const unsigned int kConferenceModeMaxBandwidthKbps = 500;
+
 class FakeViEWrapper : public cricket::ViEWrapper {
  public:
   explicit FakeViEWrapper(cricket::FakeWebRtcVideoEngine* engine)
-      : cricket::ViEWrapper(engine, engine, engine, engine,
-                            engine, engine, engine) {
+      : cricket::ViEWrapper(engine,  // base
+                            engine,  // codec
+                            engine,  // capture
+                            engine,  // network
+                            engine,  // render
+                            engine,  // rtp
+                            engine) {  // image
   }
 };
 
-class FakeNetworkInterface : public cricket::MediaChannel::NetworkInterface {
+// Test fixture to test WebRtcVideoEngine with a fake webrtc::VideoEngine.
+// Useful for testing failure paths.
+class WebRtcVideoEngineTestFake : public testing::Test {
  public:
-  FakeNetworkInterface()
-     : recv_buffer_size_(0),
-       send_buffer_size_(0) {
-  }
-  virtual bool SendPacket(talk_base::Buffer* packet) {
-    return true;
-  }
-  virtual bool SendRtcp(talk_base::Buffer* packet) {
-    return true;
-  }
-  virtual int SetOption(SocketType type, talk_base::Socket::Option opt,
-                        int option) {
-    if (type == ST_RTP) {
-     if (opt == talk_base::Socket::OPT_RCVBUF)
-       recv_buffer_size_ = option;
-     else if (opt == talk_base::Socket::OPT_SNDBUF)
-       send_buffer_size_ = option;
-    }
-    return 0;
-  }
-  virtual ~FakeNetworkInterface() {}
-  int recv_buffer_size_;
-  int send_buffer_size_;
-};
-
-class WebRtcVideoEngineTest : public testing::Test {
- public:
-  class ChannelErrorListener : public sigslot::has_slots<> {
-   public:
-    explicit ChannelErrorListener(cricket::WebRtcVideoMediaChannel* channel)
-        : ssrc_(0), error_(cricket::WebRtcVideoMediaChannel::ERROR_NONE) {
-      ASSERT(channel != NULL);
-      channel->SignalMediaError.connect(
-          this, &ChannelErrorListener::OnVideoChannelError);
-    }
-    void OnVideoChannelError(uint32 ssrc,
-                             cricket::WebRtcVideoMediaChannel::Error error) {
-      ssrc_ = ssrc;
-      error_ = error;
-    }
-    void Reset() {
-      ssrc_ = 0;
-      error_ = cricket::WebRtcVideoMediaChannel::ERROR_NONE;
-    }
-    uint32 ssrc() const {
-      return ssrc_;
-    }
-    cricket::WebRtcVideoMediaChannel::Error error() const {
-      return error_;
-    }
-
-   private:
-    uint32 ssrc_;
-    cricket::WebRtcVideoMediaChannel::Error error_;
-  };
-
-  WebRtcVideoEngineTest()
+  WebRtcVideoEngineTestFake()
       : vie_(kVideoCodecs, ARRAY_SIZE(kVideoCodecs)),
         engine_(NULL,  // cricket::WebRtcVoiceEngine
                 new FakeViEWrapper(&vie_)),
@@ -102,9 +86,19 @@
     }
     return result;
   }
-  void DeliverPacket(const void* data, int len) {
-    talk_base::Buffer packet(data, len);
-    channel_->OnPacketReceived(&packet);
+  bool SendI420Frame(int width, int height) {
+    if (NULL == channel_) {
+      return false;
+    }
+    cricket::WebRtcVideoFrame frame;
+    size_t size = width * height * 3 / 2;  // I420
+    talk_base::scoped_ptr<uint8> pixel(new uint8[size]);
+    if (!frame.Init(cricket::FOURCC_I420,
+                    width, height, width, height,
+                    pixel.get(), size, 1, 1, 0, 0, 0)) {
+      return false;
+    }
+    return channel_->SendFrame(0, &frame);
   }
   virtual void TearDown() {
     delete channel_;
@@ -118,40 +112,580 @@
   cricket::WebRtcVoiceMediaChannel* voice_channel_;
 };
 
+// Test fixtures to test WebRtcVideoEngine with a real webrtc::VideoEngine.
+class WebRtcVideoEngineTest
+    : public VideoEngineTest<cricket::WebRtcVideoEngine> {
+ protected:
+  typedef VideoEngineTest<cricket::WebRtcVideoEngine> Base;
+};
+class WebRtcVideoMediaChannelTest
+    : public VideoMediaChannelTest<
+        cricket::WebRtcVideoEngine, cricket::WebRtcVideoMediaChannel> {
+ protected:
+  typedef VideoMediaChannelTest<cricket::WebRtcVideoEngine,
+       cricket::WebRtcVideoMediaChannel> Base;
+  virtual cricket::VideoCodec DefaultCodec() { return kVP8Codec; }
+  virtual void SetUp() {
+    Base::SetUp();
+    // Need to start the capturer to allow us to pump in frames.
+    engine_.SetCapture(true);
+  }
+  virtual void TearDown() {
+    engine_.SetCapture(false);
+    Base::TearDown();
+  }
+};
+
+/////////////////////////
+// Tests with fake ViE //
+/////////////////////////
+
 // Tests that our stub library "works".
-TEST_F(WebRtcVideoEngineTest, StartupShutdown) {
+TEST_F(WebRtcVideoEngineTestFake, StartupShutdown) {
   EXPECT_FALSE(vie_.IsInited());
   EXPECT_TRUE(engine_.Init());
   EXPECT_TRUE(vie_.IsInited());
   engine_.Terminate();
-  // TODO: what to expect after Terminate
-  // EXPECT_FALSE(vie_.IsInited());
 }
 
 // Tests that we can create and destroy a channel.
-TEST_F(WebRtcVideoEngineTest, CreateChannel) {
+TEST_F(WebRtcVideoEngineTestFake, CreateChannel) {
   EXPECT_TRUE(engine_.Init());
   channel_ = engine_.CreateChannel(voice_channel_);
   EXPECT_TRUE(channel_ != NULL);
+  EXPECT_EQ(1, engine_.GetNumOfChannels());
+  delete channel_;
+  channel_ = NULL;
+  EXPECT_EQ(0, engine_.GetNumOfChannels());
 }
 
 // Tests that we properly handle failures in CreateChannel.
-TEST_F(WebRtcVideoEngineTest, CreateChannelFail) {
+TEST_F(WebRtcVideoEngineTestFake, CreateChannelFail) {
   vie_.set_fail_create_channel(true);
   EXPECT_TRUE(engine_.Init());
   channel_ = engine_.CreateChannel(voice_channel_);
   EXPECT_TRUE(channel_ == NULL);
 }
 
-// Tests that we can find codecs by name or id
+// Tests that we properly handle failures in AllocateExternalCaptureDevice.
+TEST_F(WebRtcVideoEngineTestFake, AllocateExternalCaptureDeviceFail) {
+  vie_.set_fail_alloc_capturer(true);
+  EXPECT_TRUE(engine_.Init());
+  channel_ = engine_.CreateChannel(voice_channel_);
+  EXPECT_TRUE(channel_ == NULL);
+}
+
+// Test that we apply our default codecs properly.
+TEST_F(WebRtcVideoEngineTestFake, SetSendCodecs) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::VideoCodec gcodec;
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+  EXPECT_EQ(kVP8Codec.width, gcodec.width);
+  EXPECT_EQ(kVP8Codec.height, gcodec.height);
+  EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+  EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+  EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+  EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate);
+  EXPECT_TRUE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_FALSE(vie_.GetNackStatus(channel_num));
+  // TODO: Check RTCP, PLI, TMMBR.
+}
+
+// Test that we constrain send codecs properly.
+TEST_F(WebRtcVideoEngineTestFake, ConstrainSendCodecs) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  // Set max settings of 640x400x30.
+  EXPECT_TRUE(engine_.SetDefaultEncoderConfig(
+    cricket::VideoEncoderConfig(kVP8Codec)));
+
+  // Send codec format bigger than max setting.
+  cricket::VideoCodec codec(kVP8Codec);
+  codec.width = 1280;
+  codec.height = 800;
+  codec.framerate = 60;
+  std::vector<cricket::VideoCodec> codec_list;
+  codec_list.push_back(codec);
+
+  // Set send codec and verify codec has been constrained.
+  EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+  webrtc::VideoCodec gcodec;
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+  EXPECT_EQ(kVP8Codec.width, gcodec.width);
+  EXPECT_EQ(kVP8Codec.height, gcodec.height);
+  EXPECT_EQ(kVP8Codec.framerate, gcodec.maxFramerate);
+  EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+}
+
+// Test that SetSendCodecs rejects bad format.
+TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsRejectBadFormat) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  // Set w = 0.
+  cricket::VideoCodec codec(kVP8Codec);
+  codec.width = 0;
+  std::vector<cricket::VideoCodec> codec_list;
+  codec_list.push_back(codec);
+
+  // Verify SetSendCodecs failed and send codec is not changed on engine.
+  EXPECT_FALSE(channel_->SetSendCodecs(codec_list));
+  webrtc::VideoCodec gcodec;
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(0, gcodec.plType);
+
+  // Set h = 0.
+  codec_list[0].width = 640;
+  codec_list[0].height = 0;
+
+  // Verify SetSendCodecs failed and send codec is not changed on engine.
+  EXPECT_FALSE(channel_->SetSendCodecs(codec_list));
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(0, gcodec.plType);
+}
+
+// Test that SetSendCodecs rejects bad codec.
+TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsRejectBadCodec) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  // Set bad codec name.
+  cricket::VideoCodec codec(kVP8Codec);
+  codec.name = "bad";
+  std::vector<cricket::VideoCodec> codec_list;
+  codec_list.push_back(codec);
+
+  // Verify SetSendCodecs failed and send codec is not changed on engine.
+  EXPECT_FALSE(channel_->SetSendCodecs(codec_list));
+  webrtc::VideoCodec gcodec;
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(0, gcodec.plType);
+}
+
+// Test that vie send codec is reset on new video frame size.
+TEST_F(WebRtcVideoEngineTestFake, ResetVieSendCodecOnNewFrameSize) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  // Set send codec.
+  std::vector<cricket::VideoCodec> codec_list;
+  codec_list.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(123)));
+  EXPECT_TRUE(channel_->SetSend(true));
+
+  // Capture a smaller frame and verify vie send codec has been reset to
+  // the new size.
+  SendI420Frame(kVP8Codec.width / 2, kVP8Codec.height / 2);
+  webrtc::VideoCodec gcodec;
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kVP8Codec.width / 2, gcodec.width);
+  EXPECT_EQ(kVP8Codec.height / 2, gcodec.height);
+
+  // Capture a frame bigger than send_codec_ and verify vie send codec has been
+  // reset (and clipped) to send_codec_.
+  SendI420Frame(kVP8Codec.width * 2, kVP8Codec.height * 2);
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kVP8Codec.width, gcodec.width);
+  EXPECT_EQ(kVP8Codec.height, gcodec.height);
+}
+
+// Test that we set our inbound codecs properly.
+TEST_F(WebRtcVideoEngineTestFake, SetRecvCodecs) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+
+  webrtc::VideoCodec wcodec;
+  EXPECT_TRUE(engine_.ConvertFromCricketVideoCodec(kVP8Codec, &wcodec));
+  EXPECT_TRUE(vie_.ReceiveCodecRegistered(channel_num, wcodec));
+}
+
+// Test that channel connects and disconnects external capturer correctly.
+TEST_F(WebRtcVideoEngineTestFake, HasExternalCapturer) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  EXPECT_EQ(1, vie_.GetNumCapturers());
+  int capture_id = vie_.GetCaptureId(channel_num);
+  EXPECT_EQ(channel_num, vie_.GetCaptureChannelId(capture_id));
+
+  // Delete the channel should disconnect the capturer.
+  delete channel_;
+  channel_ = NULL;
+  EXPECT_EQ(0, vie_.GetNumCapturers());
+}
+
+// Test that channel adds and removes renderer correctly.
+TEST_F(WebRtcVideoEngineTestFake, HasRenderer) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  EXPECT_TRUE(vie_.GetHasRenderer(channel_num));
+  EXPECT_FALSE(vie_.GetRenderStarted(channel_num));
+}
+
+// Test that rtcp is enabled on the channel.
+TEST_F(WebRtcVideoEngineTestFake, RtcpEnabled) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_EQ(webrtc::kRtcpCompound_RFC4585, vie_.GetRtcpStatus(channel_num));
+}
+
+// Test that key frame request method is set on the channel.
+TEST_F(WebRtcVideoEngineTestFake, KeyFrameRequestEnabled) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_EQ(webrtc::kViEKeyFrameRequestPliRtcp,
+            vie_.GetKeyFrameRequestMethod(channel_num));
+}
+
+// Test that remb is enabled on the default channel.
+TEST_F(WebRtcVideoEngineTestFake, RembEnabled) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+}
+
+// Test that remb is enabled on a receive channel but it uses the default
+// channel for sending remb packets.
+TEST_F(WebRtcVideoEngineTestFake, RembEnabledOnReceiveChannels) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  int new_channel_num = vie_.GetLastChannel();
+  EXPECT_NE(channel_num, new_channel_num);
+
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatus(new_channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(new_channel_num));
+}
+
+// Test that AddRecvStream doesn't create new channel for 1:1 call.
+TEST_F(WebRtcVideoEngineTestFake, AddRecvStream1On1) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_EQ(channel_num, vie_.GetLastChannel());
+}
+
+// Test that AddRecvStream doesn't change remb for 1:1 call.
+TEST_F(WebRtcVideoEngineTestFake, NoRembChangeAfterAddRecvStream) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+}
+
+// Test remb sending is on after StartSending and off after StopSending.
+TEST_F(WebRtcVideoEngineTestFake, RembOnOff) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  // Verify remb sending is off before StartSending.
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+
+  // Verify remb sending is on after StartSending.
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+
+  // Verify remb sending is off after StopSending.
+  EXPECT_TRUE(channel_->SetSend(false));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+}
+
+// Test that nack is enabled on the channel if we don't offer red/fec.
+TEST_F(WebRtcVideoEngineTestFake, NackEnabled) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  codecs.resize(1);  // toss out red and ulpfec
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(vie_.GetNackStatus(channel_num));
+}
+
+// Test that we enable hybrid NACK FEC mode.
+TEST_F(WebRtcVideoEngineTestFake, HybridNackFec) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_FALSE(vie_.GetNackStatus(channel_num));
+}
+
+// Test that we enable hybrid NACK FEC mode when calling SetSendCodecs and
+// SetReceiveCodecs in reversed order.
+TEST_F(WebRtcVideoEngineTestFake, HybridNackFecReversedOrder) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_FALSE(vie_.GetNackStatus(channel_num));
+}
+
+// Test NACK vs Hybrid NACK/FEC interop call setup, i.e. only use NACK even if
+// red/fec is offered as receive codec.
+TEST_F(WebRtcVideoEngineTestFake, VideoProtectionInterop) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> recv_codecs(engine_.codecs());
+  std::vector<cricket::VideoCodec> send_codecs(engine_.codecs());
+  // Only add VP8 as send codec.
+  send_codecs.resize(1);
+  EXPECT_TRUE(channel_->SetRecvCodecs(recv_codecs));
+  EXPECT_TRUE(channel_->SetSendCodecs(send_codecs));
+  EXPECT_FALSE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_TRUE(vie_.GetNackStatus(channel_num));
+}
+
+// Test NACK vs Hybrid NACK/FEC interop call setup, i.e. only use NACK even if
+// red/fec is offered as receive codec. Call order reversed compared to
+// VideoProtectionInterop.
+TEST_F(WebRtcVideoEngineTestFake, VideoProtectionInteropReversed) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> recv_codecs(engine_.codecs());
+  std::vector<cricket::VideoCodec> send_codecs(engine_.codecs());
+  // Only add VP8 as send codec.
+  send_codecs.resize(1);
+  EXPECT_TRUE(channel_->SetSendCodecs(send_codecs));
+  EXPECT_TRUE(channel_->SetRecvCodecs(recv_codecs));
+  EXPECT_FALSE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_TRUE(vie_.GetNackStatus(channel_num));
+}
+
+// Test that NACK, not hybrid mode, is enabled in conference mode.
+TEST_F(WebRtcVideoEngineTestFake, HybridNackFecConference) {
+  EXPECT_TRUE(SetupEngine());
+  // Setup the send channel.
+  int send_channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+  EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_FALSE(vie_.GetHybridNackFecStatus(send_channel_num));
+  EXPECT_TRUE(vie_.GetNackStatus(send_channel_num));
+  // Add a receive stream.
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  int receive_channel_num = vie_.GetLastChannel();
+  EXPECT_FALSE(vie_.GetHybridNackFecStatus(receive_channel_num));
+  EXPECT_TRUE(vie_.GetNackStatus(receive_channel_num));
+}
+
+// Test that we can create a channel and start/stop rendering out on it.
+TEST_F(WebRtcVideoEngineTestFake, SetRender) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  // Verify we can start/stop/start/stop rendering.
+  EXPECT_TRUE(channel_->SetRender(true));
+  EXPECT_TRUE(vie_.GetRenderStarted(channel_num));
+  EXPECT_TRUE(channel_->SetRender(false));
+  EXPECT_FALSE(vie_.GetRenderStarted(channel_num));
+  EXPECT_TRUE(channel_->SetRender(true));
+  EXPECT_TRUE(vie_.GetRenderStarted(channel_num));
+  EXPECT_TRUE(channel_->SetRender(false));
+  EXPECT_FALSE(vie_.GetRenderStarted(channel_num));
+}
+
+// Test that we can create a channel and start/stop sending out on it.
+TEST_F(WebRtcVideoEngineTestFake, SetSend) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  // Set send codecs on the channel.
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(123)));
+
+  // Verify we can start/stop/start/stop sending.
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(vie_.GetSend(channel_num));
+  EXPECT_TRUE(channel_->SetSend(false));
+  EXPECT_FALSE(vie_.GetSend(channel_num));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(vie_.GetSend(channel_num));
+  EXPECT_TRUE(channel_->SetSend(false));
+  EXPECT_FALSE(vie_.GetSend(channel_num));
+}
+
+// Test that we set bandwidth properly when using full auto bandwidth mode.
+TEST_F(WebRtcVideoEngineTestFake, SetBandwidthAuto) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetSendBandwidth(true, cricket::kAutoBandwidth));
+  webrtc::VideoCodec gcodec;
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+  EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+  EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+  EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+  EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate);
+}
+
+// Test that we set bandwidth properly when using auto with upper bound.
+TEST_F(WebRtcVideoEngineTestFake, SetBandwidthAutoCapped) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetSendBandwidth(true, 768000));
+  webrtc::VideoCodec gcodec;
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+  EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+  EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+  EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+  EXPECT_EQ(768U, gcodec.maxBitrate);
+}
+
+// Test that we set bandwidth properly when using a fixed bandwidth.
+TEST_F(WebRtcVideoEngineTestFake, SetBandwidthFixed) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetSendBandwidth(false, 768000));
+  webrtc::VideoCodec gcodec;
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+  EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+  EXPECT_EQ(768U, gcodec.minBitrate);
+  EXPECT_EQ(768U, gcodec.startBitrate);
+  EXPECT_EQ(768U, gcodec.maxBitrate);
+}
+
+// Test SetSendSsrc.
+TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAndCname) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  cricket::StreamParams stream;
+  stream.ssrcs.push_back(1234);
+  stream.cname = "cname";
+  channel_->AddSendStream(stream);
+
+  unsigned int ssrc = 0;
+  EXPECT_EQ(0, vie_.GetLocalSSRC(channel_num, ssrc));
+  EXPECT_EQ(1234U, ssrc);
+
+  char rtcp_cname[256];
+  EXPECT_EQ(0, vie_.GetRTCPCName(channel_num, rtcp_cname));
+  EXPECT_STREQ("cname", rtcp_cname);
+}
+
+// Test that the local SSRC is the same on sending and receiving channels if the
+// receive channel is created before the send channel.
+TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAfterCreatingReceiveChannel) {
+  EXPECT_TRUE(SetupEngine());
+
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  int receive_channel_num = vie_.GetLastChannel();
+  cricket::StreamParams stream = cricket::StreamParams::CreateLegacy(1234);
+  EXPECT_TRUE(channel_->AddSendStream(stream));
+  int send_channel_num = vie_.GetLastChannel();
+  unsigned int ssrc = 0;
+  EXPECT_EQ(0, vie_.GetLocalSSRC(send_channel_num, ssrc));
+  EXPECT_EQ(1234U, ssrc);
+  ssrc = 0;
+  EXPECT_EQ(0, vie_.GetLocalSSRC(receive_channel_num, ssrc));
+  EXPECT_EQ(1234U, ssrc);
+}
+
+// Test SetOptions with OPT_CONFERENCE flag.
+TEST_F(WebRtcVideoEngineTestFake, SetOptionsWithConferenceMode) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+
+  // Verify default send codec and bitrate.
+  webrtc::VideoCodec gcodec;
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+  EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+  EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+  EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+  EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate);
+
+  // Set options with OPT_CONFERENCE flag.
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+
+  // Verify channel now has new max bitrate.
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+  EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+  EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+  EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+  EXPECT_EQ(kConferenceModeMaxBandwidthKbps, gcodec.maxBitrate);
+
+  // Set options back to zero.
+  EXPECT_TRUE(channel_->SetOptions(0));
+
+  // Verify channel now has default max bitrate again.
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+  EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+  EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+  EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+  EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate);
+}
+
+/////////////////////////
+// Tests with real ViE //
+/////////////////////////
+
+// Tests that we can find codecs by name or id.
 TEST_F(WebRtcVideoEngineTest, FindCodec) {
   // We should not need to init engine in order to get codecs.
   const std::vector<cricket::VideoCodec>& c = engine_.codecs();
-  EXPECT_EQ(1U, c.size());
+  EXPECT_EQ(3U, c.size());
 
   cricket::VideoCodec vp8(104, "VP8", 320, 200, 30, 0);
   EXPECT_TRUE(engine_.FindCodec(vp8));
 
+  cricket::VideoCodec vp8_ci(104, "vp8", 320, 200, 30, 0);
+  EXPECT_TRUE(engine_.FindCodec(vp8));
+
   cricket::VideoCodec vp8_diff_fr_diff_pref(104, "VP8", 320, 200, 50, 50);
   EXPECT_TRUE(engine_.FindCodec(vp8_diff_fr_diff_pref));
 
@@ -167,59 +701,251 @@
   // Test that FindCodec can handle the case when width/height is 0.
   cricket::VideoCodec vp8_zero_res(104, "VP8", 0, 0, 30, 0);
   EXPECT_TRUE(engine_.FindCodec(vp8_zero_res));
+
+  cricket::VideoCodec red(101, "RED", 0, 0, 30, 0);
+  EXPECT_TRUE(engine_.FindCodec(red));
+
+  cricket::VideoCodec red_ci(101, "red", 0, 0, 30, 0);
+  EXPECT_TRUE(engine_.FindCodec(red));
+
+  cricket::VideoCodec fec(102, "ULPFEC", 0, 0, 30, 0);
+  EXPECT_TRUE(engine_.FindCodec(fec));
+
+  cricket::VideoCodec fec_ci(102, "ulpfec", 0, 0, 30, 0);
+  EXPECT_TRUE(engine_.FindCodec(fec));
 }
 
-// Test that we set our inbound codecs properly
-TEST_F(WebRtcVideoEngineTest, SetRecvCodecs) {
-  EXPECT_TRUE(SetupEngine());
+TEST_F(WebRtcVideoEngineTest, StartupShutdown) {
+  EXPECT_TRUE(engine_.Init());
+  engine_.Terminate();
+}
+
+TEST_PRE_VIDEOENGINE_INIT(WebRtcVideoEngineTest, ConstrainNewCodec)
+TEST_POST_VIDEOENGINE_INIT(WebRtcVideoEngineTest, ConstrainNewCodec)
+
+TEST_PRE_VIDEOENGINE_INIT(WebRtcVideoEngineTest, ConstrainRunningCodec)
+TEST_POST_VIDEOENGINE_INIT(WebRtcVideoEngineTest, ConstrainRunningCodec)
+
+// TODO: Figure out why ViE is munging the COM refcount.
+#ifdef WIN32
+TEST_F(WebRtcVideoEngineTest, DISABLED_CheckCoInitialize) {
+  Base::CheckCoInitialize();
+}
+#endif
+
+TEST_F(WebRtcVideoEngineTest, CreateChannel) {
+  EXPECT_TRUE(engine_.Init());
+  cricket::VideoMediaChannel* channel = engine_.CreateChannel(NULL);
+  EXPECT_TRUE(channel != NULL);
+  delete channel;
+}
+
+TEST_F(WebRtcVideoEngineTest, SetCaptureDevice) {
+  cricket::Device device;
+  EXPECT_TRUE(engine_.Init());
+
+  EXPECT_TRUE(engine_.SetCaptureDevice(&device));
+  EXPECT_FALSE(engine_.IsCapturing());
+  // FakeVideoCapturer returns CR_SUCCESS.
+  EXPECT_EQ(cricket::CR_SUCCESS, engine_.SetCapture(true));
+  EXPECT_TRUE(engine_.IsCapturing());
+
+  EXPECT_TRUE(engine_.SetCaptureDevice(NULL));
+  EXPECT_FALSE(engine_.IsCapturing());
+}
+
+TEST_F(WebRtcVideoEngineTest, SetCaptureModule) {
+  // Use 123 to verify there's no assumption to the module id
+  FakeWebRtcVideoCaptureModule* vcm =
+      new FakeWebRtcVideoCaptureModule(NULL, 123);
+  EXPECT_TRUE(engine_.Init());
+  // The ownership of the vcm is transferred to the engine.
+  // Technically we should call vcm->AddRef since we are using the vcm below,
+  // however the FakeWebRtcVideoCaptureModule didn't implemented the refcount.
+  // So for testing, this should be fine.
+  // The SetCaptureModule call always starts the capture.
+  EXPECT_TRUE(engine_.SetCaptureModule(vcm));
+  EXPECT_TRUE(engine_.IsCapturing());
+  EXPECT_EQ(engine_.default_codec_format().width, vcm->cap().width);
+  EXPECT_EQ(engine_.default_codec_format().height, vcm->cap().height);
+  EXPECT_EQ(cricket::VideoFormat::IntervalToFps(
+                engine_.default_codec_format().interval),
+            vcm->cap().maxFPS);
+  EXPECT_EQ(webrtc::kVideoI420, vcm->cap().rawType);
+  EXPECT_EQ(webrtc::kVideoCodecUnknown, vcm->cap().codecType);
+
+  EXPECT_TRUE(engine_.SetCaptureModule(NULL));
+  EXPECT_FALSE(engine_.IsCapturing());
+}
+
+TEST_F(WebRtcVideoEngineTest, SetVideoCapturer) {
+  // Use 123 to verify there's no assumption to the module id
+  FakeWebRtcVideoCaptureModule* vcm =
+      new FakeWebRtcVideoCaptureModule(NULL, 123);
+  talk_base::scoped_ptr<cricket::WebRtcVideoCapturer> capturer(
+      new cricket::WebRtcVideoCapturer);
+  EXPECT_TRUE(capturer->Init(vcm));
+  EXPECT_TRUE(engine_.Init());
+  const uint32 ssrc_dummy = 0;
+  EXPECT_TRUE(engine_.SetVideoCapturer(capturer.get(), ssrc_dummy));
+  EXPECT_FALSE(engine_.IsCapturing());
+  EXPECT_EQ(cricket::CR_PENDING, engine_.SetCapture(true));
+  EXPECT_TRUE(engine_.IsCapturing());
+
+  EXPECT_EQ(engine_.default_codec_format().width, vcm->cap().width);
+  EXPECT_EQ(engine_.default_codec_format().height, vcm->cap().height);
+  EXPECT_EQ(cricket::VideoFormat::IntervalToFps(
+      engine_.default_codec_format().interval),
+            vcm->cap().maxFPS);
+  EXPECT_EQ(webrtc::kVideoI420, vcm->cap().rawType);
+  EXPECT_EQ(webrtc::kVideoCodecUnknown, vcm->cap().codecType);
+
+  EXPECT_TRUE(engine_.SetVideoCapturer(NULL, ssrc_dummy));
+  EXPECT_FALSE(engine_.IsCapturing());
+}
+
+TEST_F(WebRtcVideoEngineTest, TestRegisterVideoProcessor) {
+  cricket::FakeMediaProcessor vp;
+  EXPECT_TRUE(engine_.Init());
+
+  EXPECT_TRUE(engine_.RegisterProcessor(&vp));
+  engine_.TriggerMediaFrame(0, NULL);
+  EXPECT_EQ(1, vp.video_frame_count());
+
+  EXPECT_TRUE(engine_.UnregisterProcessor(&vp));
+  engine_.TriggerMediaFrame(0, NULL);
+  EXPECT_EQ(1, vp.video_frame_count());
+
+  engine_.Terminate();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SetRecvCodecs) {
   std::vector<cricket::VideoCodec> codecs;
   codecs.push_back(kVP8Codec);
   EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
 }
+TEST_F(WebRtcVideoMediaChannelTest, SetRecvCodecsWrongPayloadType) {
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  codecs[0].id = 99;
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+}
+TEST_F(WebRtcVideoMediaChannelTest, SetRecvCodecsUnsupportedCodec) {
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  codecs.push_back(cricket::VideoCodec(101, "VP1", 640, 400, 30, 0));
+  EXPECT_FALSE(channel_->SetRecvCodecs(codecs));
+}
 
-// Test that we apply codecs properly.
-TEST_F(WebRtcVideoEngineTest, SetSendCodecs) {
-  EXPECT_TRUE(SetupEngine());
-  int channel_num = vie_.GetLastChannel();
+TEST_F(WebRtcVideoMediaChannelTest, SetSend) {
+  Base::SetSend();
+}
+TEST_F(WebRtcVideoMediaChannelTest, SetSendWithoutCodecs) {
+  Base::SetSendWithoutCodecs();
+}
+TEST_F(WebRtcVideoMediaChannelTest, SetSendSetsTransportBufferSizes) {
+  Base::SetSendSetsTransportBufferSizes();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SendAndReceiveVp8Vga) {
+  SendAndReceive(cricket::VideoCodec(100, "VP8", 640, 400, 30, 0));
+}
+TEST_F(WebRtcVideoMediaChannelTest, SendAndReceiveVp8Qvga) {
+  SendAndReceive(cricket::VideoCodec(100, "VP8", 320, 200, 30, 0));
+}
+TEST_F(WebRtcVideoMediaChannelTest, SendAndReceiveH264SvcQqvga) {
+  SendAndReceive(cricket::VideoCodec(100, "VP8", 160, 100, 30, 0));
+}
+// TODO: Figure out why this test doesn't work.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_SendManyResizeOnce) {
+  SendManyResizeOnce();
+}
+
+// TODO: Fix this test to tolerate missing stats.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_GetStats) {
+  Base::GetStats();
+}
+
+// TODO: Fix this test to tolerate missing stats.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_GetStatsMultipleRecvStreams) {
+  Base::GetStatsMultipleRecvStreams();
+}
+// TODO: Restore this test once we support multiple send streams.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_GetStatsMultipleSendStreams) {
+  Base::GetStatsMultipleSendStreams();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SetSendBandwidth) {
+  Base::SetSendBandwidth();
+}
+TEST_F(WebRtcVideoMediaChannelTest, SetSendSsrc) {
+  Base::SetSendSsrc();
+}
+TEST_F(WebRtcVideoMediaChannelTest, SetSendSsrcAfterSetCodecs) {
+  Base::SetSendSsrcAfterSetCodecs();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SetRenderer) {
+  Base::SetRenderer();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveRecvStreams) {
+  Base::AddRemoveRecvStreams();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveRecvStreamsNoConference) {
+  Base::AddRemoveRecvStreamsNoConference();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveSendStreams) {
+  Base::AddRemoveSendStreams();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SimulateConference) {
+  Base::SimulateConference();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SetOptionsFailsWhenSending) {
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+
+  // Verify SetOptions returns true on a different options.
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CPU_ADAPTATION));
+
+  // Set send codecs on the channel and start sending.
   std::vector<cricket::VideoCodec> codecs;
   codecs.push_back(kVP8Codec);
   EXPECT_TRUE(channel_->SetSendCodecs(codecs));
-  webrtc::VideoCodec gcodec;
-  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
-  EXPECT_EQ(kVP8Codec.id, gcodec.plType);
-  EXPECT_EQ(kVP8Codec.width, gcodec.width);
-  EXPECT_EQ(kVP8Codec.height, gcodec.height);
-  EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+  EXPECT_TRUE(channel_->SetSend(true));
+
+  // Verify SetOptions returns false if channel is already sending.
+  EXPECT_FALSE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+
+  // Verify SetOptions returns true with the old options.
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CPU_ADAPTATION));
 }
 
-// Tests that the rtp buffer is set properly after the SetInterface call.
-TEST_F(WebRtcVideoEngineTest, SetRtpBufferSize) {
-  EXPECT_TRUE(SetupEngine());
-  const int kExpectedVideoRtpBufferSize = 65536;
-  FakeNetworkInterface network_interface;
-  channel_->SetInterface(&network_interface);
-  EXPECT_EQ(kExpectedVideoRtpBufferSize, network_interface.recv_buffer_size_);
-  EXPECT_EQ(kExpectedVideoRtpBufferSize, network_interface.send_buffer_size_);
+// TODO: Investigate why this test is flaky.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_AdaptResolution16x10) {
+  Base::AdaptResolution16x10();
 }
-
-// TODO: add tests for below interfaces
-// bool SetOptions(int options);
-// bool SetCaptureDevice(const Device* device);
-// bool SetLocalRenderer(VideoRenderer* renderer);
-// CaptureResult SetCapture(bool capture);
-// virtual bool SetRender(bool render);
-// virtual bool SetSend(bool send);
-// virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc);
-// virtual bool RemoveStream(uint32 ssrc);
-// virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
-// virtual bool GetStats(VideoMediaInfo* info);
-// virtual bool SendIntraFrame();
-// virtual bool RequestIntraFrame();
-// virtual void OnPacketReceived(talk_base::Buffer* packet);
-// virtual void OnRtcpReceived(talk_base::Buffer* packet);
-// virtual void SetSendSsrc(uint32 id);
-// virtual bool SetRtcpCName(const std::string& cname);
-// virtual bool Mute(bool on);
-// virtual bool SetSendBandwidth(bool autobw, int bps);
-// virtual bool SetOptions(int options);
-
+// TODO: Investigate why this test is flaky.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_AdaptResolution4x3) {
+  Base::AdaptResolution4x3();
+}
+// TODO: Restore this test once we support sending 0 fps.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_AdaptDropAllFrames) {
+  Base::AdaptDropAllFrames();
+}
+// TODO: Understand why we get decode errors on this test.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_AdaptFramerate) {
+  Base::AdaptFramerate();
+}
+// TODO: Understand why format is set but the encoded frames do not
+// change.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_SetSendStreamFormat) {
+  Base::SetSendStreamFormat();
+}
+// TODO: Understand why we receive a not-quite-black frame.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_Mute) {
+  Base::Mute();
+}
diff --git a/talk/session/phone/webrtcvideoframe.cc b/talk/session/phone/webrtcvideoframe.cc
index 8d2d6ca..5eb849d 100644
--- a/talk/session/phone/webrtcvideoframe.cc
+++ b/talk/session/phone/webrtcvideoframe.cc
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * 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:
@@ -27,17 +27,20 @@
 
 #include "talk/session/phone/webrtcvideoframe.h"
 
+#include "libyuv/convert.h"
+#include "libyuv/planar_functions.h"
 #include "talk/base/logging.h"
 #include "talk/session/phone/videocapturer.h"
 #include "talk/session/phone/videocommon.h"
-#ifdef WEBRTC_RELATIVE_PATH
-#include "common_video/vplib/main/interface/vplib.h"
-#else
-#include "third_party/webrtc/files/include/vplib.h"
-#endif
 
 namespace cricket {
 
+static const int kWatermarkWidth = 8;
+static const int kWatermarkHeight = 8;
+static const int kWatermarkOffsetFromLeft = 8;
+static const int kWatermarkOffsetFromBottom = 8;
+static const unsigned char kWatermarkMaxYValue = 64;
+
 WebRtcVideoFrame::WebRtcVideoFrame() {
 }
 
@@ -49,36 +52,22 @@
                             size_t pixel_width, size_t pixel_height,
                             int64 elapsed_time, int64 time_stamp,
                             int rotation) {
-  // WebRtcVideoFrame currently doesn't support color conversion or rotation.
-  if (format != FOURCC_I420 || dw != w || dh != h || rotation != 0) {
-    return false;
-  }
-
-  uint8* buffer = new uint8[sample_size];
-  memcpy(buffer, sample, sample_size);
-  Attach(buffer, sample_size, w, h, pixel_width, pixel_height,
-         elapsed_time, time_stamp, rotation);
-  return true;
+  return Reset(format, w, h, dw, dh, sample, sample_size,
+               pixel_width, pixel_height, elapsed_time, time_stamp, rotation);
 }
 
 bool WebRtcVideoFrame::Init(const CapturedFrame* frame, int dw, int dh) {
-  return Init(frame->fourcc, frame->width, frame->height, dw, dh,
-              static_cast<uint8*>(frame->data), frame->data_size,
-              frame->pixel_width, frame->pixel_height,
-              frame->elapsed_time, frame->time_stamp, frame->rotation);
+  return Reset(frame->fourcc, frame->width, frame->height, dw, dh,
+               static_cast<uint8*>(frame->data), frame->data_size,
+               frame->pixel_width, frame->pixel_height,
+               frame->elapsed_time, frame->time_stamp, frame->rotation);
 }
 
 bool WebRtcVideoFrame::InitToBlack(int w, int h,
                                    size_t pixel_width, size_t pixel_height,
                                    int64 elapsed_time, int64 time_stamp) {
-  size_t buffer_size = VideoFrame::SizeOf(w, h);
-  uint8* buffer = new uint8[buffer_size];
-  Attach(buffer, buffer_size, w, h, pixel_width, pixel_height,
-         elapsed_time, time_stamp, 0);
-  memset(GetYPlane(), 16, w * h);
-  memset(GetUPlane(), 128, w * h / 4);
-  memset(GetVPlane(), 128, w * h / 4);
-  return true;
+  InitToEmptyBuffer(w, h, pixel_width, pixel_height, elapsed_time, time_stamp);
+  return SetToBlack();
 }
 
 void WebRtcVideoFrame::Attach(uint8* buffer, size_t buffer_size, int w, int h,
@@ -95,7 +84,7 @@
   pixel_width_ = pixel_width;
   pixel_height_ = pixel_height;
   elapsed_time_ = elapsed_time;
-  video_frame_.SetTimeStamp(static_cast<WebRtc_UWord32>(time_stamp));
+  time_stamp_ = time_stamp;
   rotation_ = rotation;
 }
 
@@ -123,15 +112,18 @@
 
 const uint8* WebRtcVideoFrame::GetUPlane() const {
   WebRtc_UWord8* buffer = video_frame_.Buffer();
-  if (buffer)
+  if (buffer) {
     buffer += (video_frame_.Width() * video_frame_.Height());
+  }
   return buffer;
 }
 
 const uint8* WebRtcVideoFrame::GetVPlane() const {
   WebRtc_UWord8* buffer = video_frame_.Buffer();
-  if (buffer)
-    buffer += (video_frame_.Width() * video_frame_.Height() * 5 / 4);
+  if (buffer) {
+    int uv_size = GetChromaSize();
+    buffer += video_frame_.Width() * video_frame_.Height() + uv_size;
+  }
   return buffer;
 }
 
@@ -142,15 +134,18 @@
 
 uint8* WebRtcVideoFrame::GetUPlane() {
   WebRtc_UWord8* buffer = video_frame_.Buffer();
-  if (buffer)
+  if (buffer) {
     buffer += (video_frame_.Width() * video_frame_.Height());
+  }
   return buffer;
 }
 
 uint8* WebRtcVideoFrame::GetVPlane() {
   WebRtc_UWord8* buffer = video_frame_.Buffer();
-  if (buffer)
-    buffer += (video_frame_.Width() * video_frame_.Height() * 5 / 4);
+  if (buffer) {
+    int uv_size = GetChromaSize();
+    buffer += video_frame_.Width() * video_frame_.Height() + uv_size;
+  }
   return buffer;
 }
 
@@ -166,7 +161,7 @@
   copy->Attach(new_buffer, new_buffer_size,
                video_frame_.Width(), video_frame_.Height(),
                pixel_width_, pixel_height_,
-               elapsed_time_, video_frame_.TimeStamp(), rotation_);
+               elapsed_time_, time_stamp_, rotation_);
   return copy;
 }
 
@@ -188,91 +183,126 @@
   return needed;
 }
 
+// TODO: Refactor into base class and share with lmi
 size_t WebRtcVideoFrame::ConvertToRgbBuffer(uint32 to_fourcc,
                                             uint8* buffer,
                                             size_t size,
-                                            size_t pitch_rgb) const {
+                                            int stride_rgb) const {
   if (!video_frame_.Buffer()) {
     return 0;
   }
-
   size_t width = video_frame_.Width();
   size_t height = video_frame_.Height();
-  // See http://www.virtualdub.org/blog/pivot/entry.php?id=190 for a good
-  // explanation of pitch and why this is the amount of space we need.
-  size_t needed = pitch_rgb * (height - 1) + 4 * width;
-
-  if (needed > size) {
+  size_t needed = (stride_rgb >= 0 ? stride_rgb : -stride_rgb) * height;
+  if (size < needed) {
     LOG(LS_WARNING) << "RGB buffer is not large enough";
-    return 0;
+    return needed;
   }
 
-  webrtc::VideoType to_type = webrtc::kUnknown;
-  switch (to_fourcc) {
-    case FOURCC_ARGB:
-      to_type = webrtc::kARGB;
-      break;
-    default:
-      LOG(LS_WARNING) << "RGB type not supported: " << to_fourcc;
-      return 0;
-  }
-
-  if (to_type != webrtc::kUnknown) {
-    webrtc::ConvertFromI420(to_type, video_frame_.Buffer(),
-                            width, height, buffer);
-  }
-
-  return needed;
-}
-
-void WebRtcVideoFrame::StretchToPlanes(
-    uint8* y, uint8* u, uint8* v,
-    int32 dst_pitch_y, int32 dst_pitch_u, int32 dst_pitch_v,
-    size_t width, size_t height, bool interpolate, bool crop) const {
-  // TODO: Implement StretchToPlanes
-}
-
-size_t WebRtcVideoFrame::StretchToBuffer(size_t w, size_t h,
-                                         uint8* buffer, size_t size,
-                                         bool interpolate,
-                                         bool crop) const {
-  if (!video_frame_.Buffer()) {
-    return 0;
-  }
-
-  size_t needed = video_frame_.Length();
-
-  if (needed <= size) {
-    uint8* bufy = buffer;
-    uint8* bufu = bufy + w * h;
-    uint8* bufv = bufu + ((w + 1) >> 1) * ((h + 1) >> 1);
-    StretchToPlanes(bufy, bufu, bufv, w, (w + 1) >> 1, (w + 1) >> 1, w, h,
-                    interpolate, crop);
+  if (libyuv::ConvertFromI420(GetYPlane(), GetYPitch(),
+                              GetUPlane(), GetUPitch(),
+                              GetVPlane(), GetVPitch(),
+                              buffer, stride_rgb,
+                              width, height,
+                              to_fourcc)) {
+    LOG(LS_WARNING) << "RGB type not supported: " << to_fourcc;
+    return 0;  // 0 indicates error
   }
   return needed;
 }
 
-void WebRtcVideoFrame::StretchToFrame(VideoFrame* target,
-    bool interpolate, bool crop) const {
-  if (!target) return;
+// Add a square watermark near the left-low corner. clamp Y.
+// Returns false on error.
+bool WebRtcVideoFrame::AddWatermark() {
+  size_t w = GetWidth();
+  size_t h = GetHeight();
 
-  StretchToPlanes(target->GetYPlane(),
-                  target->GetUPlane(),
-                  target->GetVPlane(),
-                  target->GetYPitch(),
-                  target->GetUPitch(),
-                  target->GetVPitch(),
-                  target->GetWidth(),
-                  target->GetHeight(),
-                  interpolate, crop);
-  target->SetElapsedTime(GetElapsedTime());
-  target->SetTimeStamp(GetTimeStamp());
+  if (w < kWatermarkWidth + kWatermarkOffsetFromLeft ||
+      h < kWatermarkHeight + kWatermarkOffsetFromBottom) {
+    return false;
+  }
+
+  uint8* buffer = GetYPlane();
+  for (size_t x = kWatermarkOffsetFromLeft;
+       x < kWatermarkOffsetFromLeft + kWatermarkWidth; ++x) {
+    for (size_t y = h - kWatermarkOffsetFromBottom - kWatermarkHeight;
+         y < h - kWatermarkOffsetFromBottom; ++y) {
+      buffer[y * w + x] = talk_base::_min(buffer[y * w + x],
+                                          kWatermarkMaxYValue);
+    }
+  }
+  return true;
 }
 
-VideoFrame* WebRtcVideoFrame::Stretch(size_t w, size_t h,
-    bool interpolate, bool crop) const {
-  // TODO: implement
-  return NULL;
+bool WebRtcVideoFrame::Reset(uint32 format, int w, int h, int dw, int dh,
+                             uint8* sample, size_t sample_size,
+                             size_t pixel_width, size_t pixel_height,
+                             int64 elapsed_time, int64 time_stamp,
+                             int rotation) {
+  // WebRtcVideoFrame currently doesn't support color conversion or rotation.
+  // TODO: Add horizontal cropping support.
+  if (format != FOURCC_I420 || dw != w || dh < 0 || dh > abs(h) ||
+      rotation != 0) {
+    return false;
+  }
+
+  // Discard the existing buffer.
+  uint8* old_buffer;
+  size_t old_buffer_size;
+  Detach(&old_buffer, &old_buffer_size);
+  delete[] old_buffer;
+
+  // Set up a new buffer.
+  size_t desired_size = SizeOf(dw, dh);
+  uint8* buffer = new uint8[desired_size];
+  Attach(buffer, desired_size, dw, dh, pixel_width, pixel_height,
+         elapsed_time, time_stamp, rotation);
+
+  if (dh == h) {
+    // Uncropped
+    memcpy(buffer, sample, desired_size);
+  } else {
+    // Cropped
+    // TODO: use I420Copy which supports horizontal crop and vertical
+    // flip.
+    int horiz_crop = ((w - dw) / 2) & ~1;
+    int vert_crop = ((abs(h) - dh) / 2) & ~1;
+    int y_crop_offset = w * vert_crop + horiz_crop;
+    int halfwidth = (w + 1) / 2;
+    int halfheight = (h + 1) / 2;
+    int uv_size = GetChromaSize();
+    int uv_crop_offset = (halfwidth * vert_crop + horiz_crop) / 2;
+    uint8* src_y = sample + y_crop_offset;
+    uint8* src_u = sample + w * h + uv_crop_offset;
+    uint8* src_v = sample + w * h + halfwidth * halfheight + uv_crop_offset;
+    memcpy(GetYPlane(), src_y, dw * dh);
+    memcpy(GetUPlane(), src_u, uv_size);
+    memcpy(GetVPlane(), src_v, uv_size);
+  }
+
+  return true;
+}
+
+VideoFrame* WebRtcVideoFrame::CreateEmptyFrame(int w, int h,
+                                               size_t pixel_width,
+                                               size_t pixel_height,
+                                               int64 elapsed_time,
+                                               int64 time_stamp) const {
+  WebRtcVideoFrame* frame = new WebRtcVideoFrame();
+  frame->InitToEmptyBuffer(w, h, pixel_width, pixel_height,
+                           elapsed_time, time_stamp);
+  return frame;
+}
+
+void WebRtcVideoFrame::InitToEmptyBuffer(int w, int h,
+                                         size_t pixel_width,
+                                         size_t pixel_height,
+                                         int64 elapsed_time,
+                                         int64 time_stamp) {
+  size_t buffer_size = VideoFrame::SizeOf(w, h);
+  uint8* buffer = new uint8[buffer_size];
+  Attach(buffer, buffer_size, w, h, pixel_width, pixel_height,
+         elapsed_time, time_stamp, 0);
 }
 
 }  // namespace cricket
diff --git a/talk/session/phone/webrtcvideoframe.h b/talk/session/phone/webrtcvideoframe.h
index 3431969..e041003 100644
--- a/talk/session/phone/webrtcvideoframe.h
+++ b/talk/session/phone/webrtcvideoframe.h
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * 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:
@@ -46,6 +46,10 @@
   WebRtcVideoFrame();
   ~WebRtcVideoFrame();
 
+  // Creates a frame from a raw sample with FourCC "format" and size "w" x "h".
+  // "h" can be negative indicating a vertically flipped image.
+  // "dh" is destination height if cropping is desired and is always positive.
+  // Returns "true" if successful.
   bool Init(uint32 format, int w, int h, int dw, int dh,
             uint8* sample, size_t sample_size,
             size_t pixel_width, size_t pixel_height,
@@ -60,8 +64,14 @@
               size_t pixel_width, size_t pixel_height,
               int64 elapsed_time, int64 time_stamp, int rotation);
   void Detach(uint8** buffer, size_t* buffer_size);
+  bool AddWatermark();
+  webrtc::VideoFrame* frame() { return &video_frame_; }
 
-  bool HasImage() const { return video_frame_.Buffer() != NULL; }
+  // From base class VideoFrame.
+  virtual bool Reset(uint32 format, int w, int h, int dw, int dh,
+                   uint8* sample, size_t sample_size,
+                   size_t pixel_width, size_t pixel_height,
+                   int64 elapsed_time, int64 time_stamp, int rotation);
 
   virtual size_t GetWidth() const;
   virtual size_t GetHeight() const;
@@ -72,18 +82,18 @@
   virtual uint8* GetUPlane();
   virtual uint8* GetVPlane();
   virtual int32 GetYPitch() const { return video_frame_.Width(); }
-  virtual int32 GetUPitch() const { return video_frame_.Width() / 2; }
-  virtual int32 GetVPitch() const { return video_frame_.Width() / 2; }
+  virtual int32 GetUPitch() const { return (video_frame_.Width() + 1) / 2; }
+  virtual int32 GetVPitch() const { return (video_frame_.Width() + 1) / 2; }
 
   virtual size_t GetPixelWidth() const { return pixel_width_; }
   virtual size_t GetPixelHeight() const { return pixel_height_; }
   virtual int64 GetElapsedTime() const { return elapsed_time_; }
-  virtual int64 GetTimeStamp() const { return video_frame_.TimeStamp(); }
+  virtual int64 GetTimeStamp() const { return time_stamp_; }
   virtual void SetElapsedTime(int64 elapsed_time) {
     elapsed_time_ = elapsed_time;
   }
   virtual void SetTimeStamp(int64 time_stamp) {
-    video_frame_.SetTimeStamp(static_cast<WebRtc_UWord32>(time_stamp));
+    time_stamp_ = time_stamp;
   }
 
   virtual int GetRotation() const { return rotation_; }
@@ -92,23 +102,22 @@
   virtual bool MakeExclusive();
   virtual size_t CopyToBuffer(uint8* buffer, size_t size) const;
   virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8* buffer,
-                                    size_t size, size_t pitch_rgb) const;
-  virtual void StretchToPlanes(uint8* y, uint8* u, uint8* v,
-                               int32 pitchY, int32 pitchU, int32 pitchV,
-                               size_t width, size_t height,
-                               bool interpolate, bool crop) const;
-  virtual size_t StretchToBuffer(size_t w, size_t h, uint8* buffer, size_t size,
-                                 bool interpolate, bool crop) const;
-  virtual void StretchToFrame(VideoFrame* target, bool interpolate,
-                              bool crop) const;
-  virtual VideoFrame* Stretch(size_t w, size_t h, bool interpolate,
-                              bool crop) const;
+                                    size_t size, int stride_rgb) const;
 
  private:
+  virtual VideoFrame* CreateEmptyFrame(int w, int h,
+                                       size_t pixel_width, size_t pixel_height,
+                                       int64 elapsed_time,
+                                       int64 time_stamp) const;
+  void InitToEmptyBuffer(int w, int h,
+                         size_t pixel_width, size_t pixel_height,
+                         int64 elapsed_time, int64 time_stamp);
+
   webrtc::VideoFrame video_frame_;
   size_t pixel_width_;
   size_t pixel_height_;
   int64 elapsed_time_;
+  int64 time_stamp_;
   int rotation_;
 };
 }  // namespace cricket
diff --git a/talk/session/phone/webrtcvideoframe_unittest.cc b/talk/session/phone/webrtcvideoframe_unittest.cc
index cd1f033..16e5e16 100644
--- a/talk/session/phone/webrtcvideoframe_unittest.cc
+++ b/talk/session/phone/webrtcvideoframe_unittest.cc
@@ -1,439 +1,201 @@
-// Copyright 2008 Google Inc. All Rights Reserved,
-//
-// Author: Justin Uberti (juberti@google.com)
-//         Frank Barchard (fbarchard@google.com)
-#include <string>
+/*
+ * libjingle
+ * Copyright 2011 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 
 #include "talk/base/flags.h"
-#include "talk/base/gunit.h"
-#include "talk/base/pathutils.h"
-#include "talk/base/scoped_ptr.h"
-#include "talk/base/stream.h"
-#include "talk/base/stringutils.h"
-#include "talk/session/phone/formatconversion.h"
+#include "talk/session/phone/videoframe_unittest.h"
 #include "talk/session/phone/webrtcvideoframe.h"
-#include "talk/session/phone/testutils.h"
-#include "talk/session/phone/videocommon.h"
 
-enum {
-  ROTATION_0 = 0,
-  ROTATION_90 = 90,
-  ROTATION_180 = 180,
-  ROTATION_270 = 270
-};
+extern int FLAG_yuvconverter_repeat; // from lmivideoframe_unittest.cc
 
-using cricket::WebRtcVideoFrame;
-using cricket::FOURCC_I420;
-
-static const int kWidth = 1280;
-static const int kHeight = 720;
-static const int kAlignment = 16;
-static const std::string kImageFilename = "faces.1280x720_P420.yuv";
-
-class WebRtcVideoFrameTest : public testing::Test {
- protected:
-  virtual void SetUp() {
-    // TODO: Fix (add a new flag) or remove repeat_.
-    repeat_ = 1;
-  }
-
+class WebRtcVideoFrameTest : public VideoFrameTest<cricket::WebRtcVideoFrame> {
  public:
-  // Load a video frame from disk or a buffer.
-  bool LoadFrame(const std::string& filename, uint32 format,
-                 int32 width, int32 height, WebRtcVideoFrame* frame,
-                 int rotation) {
-    talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(filename));
-    return LoadFrame(ms.get(), format, width, height, frame, rotation);
+  WebRtcVideoFrameTest() {
+    repeat_ = FLAG_yuvconverter_repeat;
   }
-
-  bool LoadFrame(talk_base::MemoryStream* ms, uint32 format,
-                 int32 width, int32 height, WebRtcVideoFrame* frame,
-                 int rotation) {
-    if (!ms) {
-      return false;
-    }
-    size_t data_size;
-    bool ret = ms->GetSize(&data_size);
-    EXPECT_TRUE(ret);
-    if (ret) {
-      ret = LoadFrame(reinterpret_cast<uint8*>(ms->GetBuffer()), data_size,
-                      format, width, height, frame, rotation);
-    }
-    return ret;
-  }
-
-  bool LoadFrame(uint8* sample, size_t sample_size, uint32 format,
-                 int32 width, int32 height, WebRtcVideoFrame* frame,
-                 int rotation) {
-    for (int i = 0; i < repeat_; ++i) {
-      if (!frame->Init(format, width, height, width, height,
-                       sample, sample_size, 1, 1, 0, 0, 0)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  talk_base::MemoryStream* LoadSample(const std::string& filename) {
-    talk_base::Pathname path(cricket::GetTestFilePath(filename));
-    talk_base::scoped_ptr<talk_base::FileStream> fs(
-        talk_base::Filesystem::OpenFile(path, "rb"));
-    if (!fs.get()) {
-      return NULL;
-    }
-
-    char buf[4096];
-    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
-        new talk_base::MemoryStream());
-    talk_base::StreamResult res = Flow(fs.get(), buf, sizeof(buf), ms.get());
-    if (res != talk_base::SR_SUCCESS) {
-      return NULL;
-    }
-
-    return ms.release();
-  }
-
-  // Write an I420 frame out to disk.
-  bool DumpFrame(const std::string& prefix,
-                 const WebRtcVideoFrame& frame) {
-    char filename[256];
-    talk_base::sprintfn(filename, sizeof(filename), "%s.%dx%d_P420.yuv",
-                        prefix.c_str(), frame.GetWidth(), frame.GetHeight());
-    size_t out_size = cricket::VideoFrame::SizeOf(frame.GetWidth(),
-                                                  frame.GetHeight());
-    talk_base::scoped_array<uint8> out(new uint8[out_size]);
-    frame.CopyToBuffer(out.get(), out_size);
-    return DumpSample(filename, out.get(), out_size);
-  }
-
-  bool DumpSample(const std::string& filename, const void* buffer, int size) {
-    talk_base::Pathname path(filename);
-    talk_base::scoped_ptr<talk_base::FileStream> fs(
-        talk_base::Filesystem::OpenFile(path, "wb"));
-    if (!fs.get()) {
-      return false;
-    }
-
-    return (fs->Write(buffer, size, NULL, NULL) == talk_base::SR_SUCCESS);
-  }
-
-  // Create a test image for YUV 420 formats with 12 bits per pixel.
-  talk_base::MemoryStream* CreateYuv420Sample(uint32 width, uint32 height) {
-    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
-        new talk_base::MemoryStream);
-    if (!ms->ReserveSize(width * height * 12 / 8)) {
-      return NULL;
-    }
-
-    for (uint32 i = 0; i < width * height * 12 / 8; ++i) {
-      char value = ((i / 63) & 1) ? 192 : 64;
-      ms->Write(&value, sizeof(value), NULL, NULL);
-    }
-    return ms.release();
-  }
-
-  talk_base::MemoryStream* CreateRgbSample(uint32 fourcc,
-                                           uint32 width, uint32 height) {
-    int r_pos, g_pos, b_pos, bytes;
-    if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) {
-      return NULL;
-    }
-
-    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
-        new talk_base::MemoryStream);
-    if (!ms->ReserveSize(width * height * bytes)) {
-      return NULL;
-    }
-
-    for (uint32 y = 0; y < height; ++y) {
-      for (uint32 x = 0; x < width; ++x) {
-        uint8 rgb[4] = { 255, 255, 255, 255 };
-        rgb[r_pos] = ((x / 63) & 1) ? 224 : 32;
-        rgb[g_pos] = (x % 63 + y % 63) + 96;
-        rgb[b_pos] = ((y / 63) & 1) ? 224 : 32;
-        ms->Write(rgb, bytes, NULL, NULL);
-      }
-    }
-    return ms.release();
-  }
-
-  // Convert RGB to 420.
-  // A negative height inverts the image.
-  bool ConvertRgb(const talk_base::MemoryStream* ms,
-                  uint32 fourcc, int32 width, int32 height,
-                  WebRtcVideoFrame* frame) {
-    int r_pos, g_pos, b_pos, bytes;
-    if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) {
-      return false;
-    }
-    int stride = width * bytes;
-    const uint8* start = reinterpret_cast<const uint8*>(ms->GetBuffer());
-    if (height < 0) {
-      height = -height;
-      start = start + stride * (height - 1);
-      stride = -stride;
-    }
-    frame->InitToBlack(width, height, 1, 1, 0, 0);
-    for (int32 y = 0; y < height; y += 2) {
-      for (int32 x = 0; x < width; x += 2) {
-        const uint8* rgb[4];
-        uint8 yuv[4][3];
-        rgb[0] = start + y * stride + x * bytes;
-        rgb[1] = rgb[0] + bytes;
-        rgb[2] = rgb[0] + stride;
-        rgb[3] = rgb[2] + bytes;
-        for (size_t i = 0; i < 4; ++i) {
-          ConvertRgbPixel(rgb[i][r_pos], rgb[i][g_pos], rgb[i][b_pos],
-                          &yuv[i][0], &yuv[i][1], &yuv[i][2]);
-        }
-        frame->GetYPlane()[width * y + x] = yuv[0][0];
-        frame->GetYPlane()[width * y + x + 1] = yuv[1][0];
-        frame->GetYPlane()[width * (y + 1) + x] = yuv[2][0];
-        frame->GetYPlane()[width * (y + 1) + x + 1] = yuv[3][0];
-        frame->GetUPlane()[width / 2 * (y / 2) + x / 2] =
-            (yuv[0][1] + yuv[1][1] + yuv[2][1] + yuv[3][1] + 2) / 4;
-        frame->GetVPlane()[width / 2 * (y / 2) + x / 2] =
-            (yuv[0][2] + yuv[1][2] + yuv[2][2] + yuv[3][2] + 2) / 4;
-      }
-    }
-    return true;
-  }
-
-  // Simple and slow RGB->YUV conversion. From NTSC standard, c/o Wikipedia.
-  void ConvertRgbPixel(uint8 r, uint8 g, uint8 b,
-                       uint8* y, uint8* u, uint8* v) {
-    *y = static_cast<int>(.257 * r + .504 * g + .098 * b) + 16;
-    *u = static_cast<int>(-.148 * r - .291 * g + .439 * b) + 128;
-    *v = static_cast<int>(.439 * r - .368 * g - .071 * b) + 128;
-  }
-
-  bool GetRgbPacking(uint32 fourcc,
-                     int* r_pos, int* g_pos, int* b_pos, int* bytes) {
-    if (fourcc == cricket::FOURCC_RAW) {
-      *r_pos = 0;
-      *g_pos = 1;
-      *b_pos = 2;
-      *bytes = 3;  // RGB in memory
-    } else if (fourcc == cricket::FOURCC_24BG) {
-      *r_pos = 2;
-      *g_pos = 1;
-      *b_pos = 0;
-      *bytes = 3;  // BGR in memory
-    } else if (fourcc == cricket::FOURCC_ABGR) {
-      *r_pos = 0;
-      *g_pos = 1;
-      *b_pos = 2;
-      *bytes = 4;  // RGBA in memory
-    } else if (fourcc == cricket::FOURCC_BGRA) {
-      *r_pos = 1;
-      *g_pos = 2;
-      *b_pos = 3;
-      *bytes = 4;  // ARGB in memory
-    } else if (fourcc == cricket::FOURCC_ARGB) {
-      *r_pos = 2;
-      *g_pos = 1;
-      *b_pos = 0;
-      *bytes = 4;  // BGRA in memory
-    } else {
-      return false;
-    }
-    return true;
-  }
-
-  // Comparison functions for testing.
-  static bool IsNull(const WebRtcVideoFrame& frame) {
-    return !frame.HasImage();
-  }
-
-  static bool IsSize(const WebRtcVideoFrame& frame,
-                     uint32 width, uint32 height) {
-    return frame.HasImage() &&
-        frame.GetYPitch() >= static_cast<int32>(width) &&
-        frame.GetUPitch() >= static_cast<int32>(width) / 2 &&
-        frame.GetVPitch() >= static_cast<int32>(width) / 2 &&
-        frame.GetWidth() == width && frame.GetHeight() == height;
-  }
-
-  static bool IsPlaneEqual(const std::string& name,
-                           const uint8* plane1, uint32 pitch1,
-                           const uint8* plane2, uint32 pitch2,
-                           uint32 width, uint32 height,
-                           int max_error) {
-    const uint8* r1 = plane1;
-    const uint8* r2 = plane2;
-    for (uint32 y = 0; y < height; ++y) {
-      for (uint32 x = 0; x < width; ++x) {
-        if (abs(static_cast<int>(r1[x] - r2[x])) > max_error) {
-          LOG(LS_INFO) << "IsPlaneEqual(" << name << "): pixel["
-                       << x << "," << y << "] differs: "
-                       << static_cast<int>(r1[x]) << " vs "
-                       << static_cast<int>(r2[x]);
-          return false;
-        }
-      }
-      r1 += pitch1;
-      r2 += pitch2;
-    }
-    return true;
-  }
-
-  static bool IsFrameContiguous(const WebRtcVideoFrame& frame) {
-    int width = frame.GetWidth();
-    int height = frame.GetHeight();
-    const uint8* y = frame.GetYPlane();
-    const uint8* u = frame.GetUPlane();
-    const uint8* v = frame.GetVPlane();
-    int size = width * height * 3 / 2;
-    bool u_near = (u - y) < size;
-    bool v_near = (v - y) < size;
-    return u_near && v_near;
-  }
-
-  static bool IsEqual(const WebRtcVideoFrame& frame,
-                      size_t width, size_t height,
-                      size_t pixel_width, size_t pixel_height,
-                      int64 elapsed_time, int64 time_stamp,
-                      const uint8* y, uint32 ypitch,
-                      const uint8* u, uint32 upitch,
-                      const uint8* v, uint32 vpitch,
-                      int max_error) {
-    if (!IsFrameContiguous(frame)) {
-      LOG(LS_INFO) << "lmi frame is not contiguous";
-    }
-    return IsSize(frame, width, height) &&
-        frame.GetPixelWidth() == pixel_width &&
-        frame.GetPixelHeight() == pixel_height &&
-        frame.GetElapsedTime() == elapsed_time &&
-        frame.GetTimeStamp() == time_stamp &&
-        IsPlaneEqual("y", frame.GetYPlane(), frame.GetYPitch(), y, ypitch,
-                     width, height, max_error) &&
-        IsPlaneEqual("u", frame.GetUPlane(), frame.GetUPitch(), u, upitch,
-                     width / 2, height / 2, max_error) &&
-        IsPlaneEqual("v", frame.GetVPlane(), frame.GetVPitch(), v, vpitch,
-                     width / 2, height / 2, max_error);
-  }
-
-  static bool IsEqual(const WebRtcVideoFrame& frame1,
-                      const WebRtcVideoFrame& frame2,
-                      int max_error) {
-    return IsEqual(frame1, frame2.GetWidth(), frame2.GetHeight(),
-                frame2.GetPixelWidth(), frame2.GetPixelHeight(),
-                frame2.GetElapsedTime(), frame2.GetTimeStamp(),
-                frame2.GetYPlane(), frame2.GetYPitch(),
-                frame2.GetUPlane(), frame2.GetUPitch(),
-                frame2.GetVPlane(), frame2.GetVPitch(),
-                max_error);
-  }
-
- protected:
-  int repeat_;
 };
 
-TEST_F(WebRtcVideoFrameTest, ConvertToARGBBuffer) {
-  size_t out_size = kWidth * kHeight * 4;
-  talk_base::scoped_array<uint8> outbuf(new uint8[out_size + kAlignment]);
-  uint8 *out = ALIGNP(outbuf.get(), kAlignment);
-  WebRtcVideoFrame frame;
-  ASSERT_TRUE(LoadFrame(kImageFilename, FOURCC_I420, kWidth, kHeight,
-                        &frame, ROTATION_0));
-
-  // TODO: Add test to convert these back to I420, to ensure the
-  // conversion is done correctly.
-  for (int i = 0; i < repeat_; ++i) {
-    EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB,
-                                                 out,
-                                                 out_size, kWidth * 4));
-  }
+#define TEST_WEBRTCVIDEOFRAME(X) TEST_F(WebRtcVideoFrameTest, X) { \
+  VideoFrameTest<cricket::WebRtcVideoFrame>::X(); \
 }
 
-// Test basic contruction of an image from an I420 buffer.
-TEST_F(WebRtcVideoFrameTest, InitI420) {
-  WebRtcVideoFrame frame;
-  EXPECT_TRUE(IsNull(frame));
-  talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(kImageFilename));
-  ASSERT_TRUE(ms.get() != NULL);
-  size_t data_size;
-  ASSERT_TRUE(ms->GetSize(&data_size));
-  uint8* buf = reinterpret_cast<uint8*>(ms->GetBuffer());
-  EXPECT_TRUE(LoadFrame(buf, data_size, FOURCC_I420,
-                        kWidth, kHeight, &frame, ROTATION_0));
+TEST_WEBRTCVIDEOFRAME(ConstructI420)
+TEST_WEBRTCVIDEOFRAME(ConstructI4201Pixel)
+// TODO: WebRtcVideoFrame does not support horizontal crop.
+// Re-evaluate once it supports 3 independent planes, since we might want to
+// just Init normally and then crop by adjusting pointers.
+// TEST_WEBRTCVIDEOFRAME(ConstructI420CropHorizontal)
+TEST_WEBRTCVIDEOFRAME(ConstructI420CropVertical)
+// TODO: WebRtcVideoFrame is not currently refcounted.
+// TEST_WEBRTCVIDEOFRAME(ConstructCopy)
+// TEST_WEBRTCVIDEOFRAME(ConstructCopyIsRef)
+TEST_WEBRTCVIDEOFRAME(ConstructBlack)
+// TODO: Implement Jpeg
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI420)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI422)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI444)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI400)
+// TODO: WebRtcVideoFrame does not support odd sizes.
+// Re-evaluate once WebRTC switches to libyuv
+// TEST_WEBRTCVIDEOFRAME(ConstructYuy2AllSizes)
+TEST_WEBRTCVIDEOFRAME(Reset)
+TEST_WEBRTCVIDEOFRAME(ConvertToABGRBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToABGRBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToABGRBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB1555Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB1555BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB1555BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB4444Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB4444BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB4444BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGBBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGBBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGBBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBGRABuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBGRABufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBGRABufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToRAWBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToRAWBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToRAWBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB24Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB24BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB24BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB565Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB565BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB565BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerBGGRBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerBGGRBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerBGGRBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGRBGBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGRBGBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGRBGBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGBRGBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGBRGBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGBRGBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerRGGBBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerRGGBBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerRGGBBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToI400Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToI400BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToI400BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToYUY2Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToYUY2BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToYUY2BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToUYVYBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToUYVYBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToUYVYBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromABGRBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromABGRBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromABGRBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB1555Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB1555BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB1555BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB4444Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB4444BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB4444BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGBBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGBBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGBBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBGRABuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBGRABufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBGRABufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRAWBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRAWBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRAWBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB24Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB24BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB24BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB565Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB565BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB565BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerBGGRBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerBGGRBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerBGGRBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGRBGBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGRBGBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGRBGBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGBRGBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGBRGBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGBRGBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerRGGBBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerRGGBBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerRGGBBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromI400Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromI400BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromI400BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromYUY2Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromYUY2BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromYUY2BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromUYVYBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromUYVYBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromUYVYBufferInverted)
+//TEST_WEBRTCVIDEOFRAME(ConvertToI422Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerGRBG)
+TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerGBRG)
+TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerBGGR)
+TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerRGGB)
+TEST_WEBRTCVIDEOFRAME(CopyToBuffer)
+TEST_WEBRTCVIDEOFRAME(CopyToBuffer1Pixel)
+//TEST_WEBRTCVIDEOFRAME(ConstructARGBBlackWhitePixel)
 
-  const uint8* y = reinterpret_cast<uint8*>(ms->GetBuffer());
-  const uint8* u = y + kWidth * kHeight;
-  const uint8* v = u + kWidth * kHeight / 4;
-  EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, 1, 1, 0, 0,
-                      y, kWidth, u, kWidth / 2, v, kWidth / 2, 0));
+TEST_WEBRTCVIDEOFRAME(StretchToFrame)
+TEST_WEBRTCVIDEOFRAME(Copy)
+// TODO: WebRtcVideoFrame is not currently refcounted.
+// TEST_WEBRTCVIDEOFRAME(CopyIsRef)
+TEST_WEBRTCVIDEOFRAME(MakeExclusive)
+
+// These functions test implementation-specific details.
+TEST_F(WebRtcVideoFrameTest, AttachAndRelease) {
+  cricket::WebRtcVideoFrame frame1, frame2;
+  ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+  const int64 time_stamp = 0x7FFFFFFFFFFFFFF0LL;
+  frame1.SetTimeStamp(time_stamp);
+  EXPECT_EQ(time_stamp, frame1.GetTimeStamp());
+  frame2.Attach(frame1.frame()->Buffer(), frame1.frame()->Size(),
+                kWidth, kHeight, 1, 1,
+                frame1.GetElapsedTime(), frame1.GetTimeStamp(), 0);
+  EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+  uint8* buffer;
+  size_t size;
+  frame2.Detach(&buffer, &size);
+  EXPECT_EQ(frame1.frame()->Buffer(), buffer);
+  EXPECT_EQ(frame1.frame()->Size(), size);
+  EXPECT_TRUE(IsNull(frame2));
+  EXPECT_TRUE(IsSize(frame1, kWidth, kHeight));
 }
 
-// Test constructing an image from a I420 buffer
-TEST_F(WebRtcVideoFrameTest, ConstructI420) {
-  WebRtcVideoFrame frame;
-  talk_base::scoped_ptr<talk_base::MemoryStream> ms(
-      CreateYuv420Sample(kWidth, kHeight));
-  EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420,
-                        kWidth, kHeight, &frame, ROTATION_0));
-
-  const uint8* y = reinterpret_cast<uint8*>(ms.get()->GetBuffer());
-  const uint8* u = y + kWidth * kHeight;
-  const uint8* v = u + kWidth * kHeight / 4;
-  EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, 1, 1, 0, 0,
-                      y, kWidth, u, kWidth / 2, v, kWidth / 2, 0));
+TEST_F(WebRtcVideoFrameTest, Transfer) {
+  cricket::WebRtcVideoFrame frame1, frame2;
+  ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+  uint8* buffer;
+  size_t size;
+  frame1.Detach(&buffer, &size),
+  frame2.Attach(buffer, size, kWidth, kHeight, 1, 1,
+                frame1.GetElapsedTime(), frame1.GetTimeStamp(), 0);
+  EXPECT_TRUE(IsNull(frame1));
+  EXPECT_TRUE(IsSize(frame2, kWidth, kHeight));
 }
 
-// Test creating an empty image and initing it to black.
-TEST_F(WebRtcVideoFrameTest, ConstructBlack) {
-  WebRtcVideoFrame frame;
-  for (int i = 0; i < repeat_; ++i) {
-    EXPECT_TRUE(frame.InitToBlack(kWidth, kHeight, 1, 1, 0, 0));
-  }
-  EXPECT_TRUE(IsSize(frame, kWidth, kHeight));
-  EXPECT_EQ(16, *frame.GetYPlane());
-  EXPECT_EQ(128, *frame.GetUPlane());
-  EXPECT_EQ(128, *frame.GetVPlane());
-}
-
-TEST_F(WebRtcVideoFrameTest, Copy) {
-  WebRtcVideoFrame frame1;
-  talk_base::scoped_ptr<cricket::WebRtcVideoFrame> frame2;
-  ASSERT_TRUE(LoadFrame(kImageFilename, FOURCC_I420, kWidth, kHeight,
-                        &frame1, ROTATION_0));
-  frame2.reset(static_cast<WebRtcVideoFrame*>(frame1.Copy()));
-  EXPECT_TRUE(IsEqual(frame1, *frame2.get(), 0));
-}
-
-TEST_F(WebRtcVideoFrameTest, CopyToBuffer) {
-  size_t out_size = kWidth * kHeight * 3 / 2;
-  talk_base::scoped_array<uint8> out(new uint8[out_size]);
-  WebRtcVideoFrame frame;
-  talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(kImageFilename));
-  ASSERT_TRUE(ms.get() != NULL);
-  size_t data_size;
-  ASSERT_TRUE(ms->GetSize(&data_size));
-  EXPECT_TRUE(LoadFrame(reinterpret_cast<uint8*>(ms->GetBuffer()),
-                        data_size, FOURCC_I420,
-                        kWidth, kHeight, &frame, ROTATION_0));
-  for (int i = 0; i < repeat_; ++i) {
-    EXPECT_EQ(out_size, frame.CopyToBuffer(out.get(), out_size));
-  }
-  EXPECT_EQ(0, memcmp(out.get(), ms->GetBuffer(), out_size));
-}
-
-TEST_F(WebRtcVideoFrameTest, CopyToBuffer1Pixel) {
-  size_t out_size = 3;
-  talk_base::scoped_array<uint8> out(new uint8[out_size + 1]);
-  memset(out.get(), 0xfb, out_size + 1);  // Fill buffer
-  uint8 pixel[3] = { 1, 2, 3 };
-  WebRtcVideoFrame frame;
-  EXPECT_TRUE(LoadFrame(pixel, sizeof(pixel), FOURCC_I420,
-                        1, 1, &frame, ROTATION_0));
-  for (int i = 0; i < repeat_; ++i) {
-    EXPECT_EQ(out_size, frame.CopyToBuffer(out.get(), out_size));
-  }
-  EXPECT_EQ(1, out.get()[0]);  // Check Y.  Should be 1.
-  EXPECT_EQ(2, out.get()[1]);  // Check U.  Should be 2.
-  EXPECT_EQ(3, out.get()[2]);  // Check V.  Should be 3.
-  EXPECT_EQ(0xfb, out.get()[3]);  // Check sentinel is still intact.
-}
-
-// TODO: Merge this with the LmiVideoFrame test for more test cases
-// when they are supported.
diff --git a/talk/session/phone/webrtcvie.h b/talk/session/phone/webrtcvie.h
index d6ef007..75fe837 100644
--- a/talk/session/phone/webrtcvie.h
+++ b/talk/session/phone/webrtcvie.h
@@ -37,14 +37,14 @@
 #include "modules/interface/module_common_types.h"
 #include "modules/video_capture/main/interface/video_capture.h"
 #include "modules/video_render/main/interface/video_render.h"
-#include "video_engine/main/interface/vie_base.h"
-#include "video_engine/main/interface/vie_capture.h"
-#include "video_engine/main/interface/vie_codec.h"
-#include "video_engine/main/interface/vie_errors.h"
-#include "video_engine/main/interface/vie_image_process.h"
-#include "video_engine/main/interface/vie_network.h"
-#include "video_engine/main/interface/vie_render.h"
-#include "video_engine/main/interface/vie_rtp_rtcp.h"
+#include "video_engine/include/vie_base.h"
+#include "video_engine/include/vie_capture.h"
+#include "video_engine/include/vie_codec.h"
+#include "video_engine/include/vie_errors.h"
+#include "video_engine/include/vie_image_process.h"
+#include "video_engine/include/vie_network.h"
+#include "video_engine/include/vie_render.h"
+#include "video_engine/include/vie_rtp_rtcp.h"
 #else
 #include "third_party/webrtc/files/include/common_types.h"
 #include "third_party/webrtc/files/include/module_common_types.h"
@@ -126,7 +126,7 @@
   webrtc::ViENetwork* network() { return network_.get(); }
   webrtc::ViERender* render() { return render_.get(); }
   webrtc::ViERTP_RTCP* rtp() { return rtp_.get(); }
-  webrtc::ViEImageProcess* sync() { return image_.get(); }
+  webrtc::ViEImageProcess* image() { return image_.get(); }
   int error() { return base_->LastError(); }
 
  private:
@@ -139,6 +139,23 @@
   scoped_vie_ptr<webrtc::ViERTP_RTCP> rtp_;
   scoped_vie_ptr<webrtc::ViEImageProcess> image_;
 };
-}
+
+// Adds indirection to static WebRtc functions, allowing them to be mocked.
+class ViETraceWrapper {
+ public:
+  virtual ~ViETraceWrapper() {}
+
+  virtual int SetTraceFilter(const unsigned int filter) {
+    return webrtc::VideoEngine::SetTraceFilter(filter);
+  }
+  virtual int SetTraceFile(const char* fileNameUTF8) {
+    return webrtc::VideoEngine::SetTraceFile(fileNameUTF8);
+  }
+  virtual int SetTraceCallback(webrtc::TraceCallback* callback) {
+    return webrtc::VideoEngine::SetTraceCallback(callback);
+  }
+};
+
+}  // namespace cricket
 
 #endif  // TALK_SESSION_PHONE_WEBRTCVIE_H_
diff --git a/talk/session/phone/webrtcvoe.h b/talk/session/phone/webrtcvoe.h
index 398928e..709b3d6 100644
--- a/talk/session/phone/webrtcvoe.h
+++ b/talk/session/phone/webrtcvoe.h
@@ -191,6 +191,7 @@
     return webrtc::VoiceEngine::SetTraceCallback(callback);
   }
 };
-}
+
+}  // namespace cricket
 
 #endif  // TALK_SESSION_PHONE_WEBRTCVOE_H_
diff --git a/talk/session/phone/webrtcvoiceengine.cc b/talk/session/phone/webrtcvoiceengine.cc
index 3dfad11..98a1b85 100644
--- a/talk/session/phone/webrtcvoiceengine.cc
+++ b/talk/session/phone/webrtcvoiceengine.cc
@@ -25,6 +25,9 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
 
 #ifdef HAVE_WEBRTC_VOICE
 
@@ -42,6 +45,8 @@
 #include "talk/base/logging.h"
 #include "talk/base/stringencode.h"
 #include "talk/base/stringutils.h"
+#include "talk/session/phone/streamparams.h"
+#include "talk/session/phone/voiceprocessor.h"
 #include "talk/session/phone/webrtcvoe.h"
 
 #ifdef WIN32
@@ -301,6 +306,10 @@
     adm_sc_ = NULL;
   }
 
+  // Test to see if the media processor was deregistered properly
+  ASSERT(SignalRxMediaFrame.is_empty());
+  ASSERT(SignalTxMediaFrame.is_empty());
+
   tracing_->SetTraceCallback(NULL);
 }
 
@@ -373,17 +382,23 @@
   // First check whether there is a valid sound device for playback.
   // TODO: Clean this up when we support setting the soundclip device.
 #ifdef WIN32
-  int num_of_devices = 0;
-  if (voe_wrapper_sc_->hw()->GetNumOfPlayoutDevices(num_of_devices) != -1 &&
-      num_of_devices > 0) {
-    if (voe_wrapper_sc_->hw()->SetPlayoutDevice(kDefaultSoundclipDeviceId)
-        == -1) {
-      LOG_RTCERR1_EX(SetPlayoutDevice, kDefaultSoundclipDeviceId,
-                      voe_wrapper_sc_->error());
-      return false;
+  // The SetPlayoutDevice may not be implemented in the case of external ADM.
+  // TODO: We should only check the adm_sc_ here, but current
+  // PeerConnection interface never set the adm_sc_, so need to check both
+  // in order to determine if the external adm is used.
+  if (!adm_ && !adm_sc_) {
+    int num_of_devices = 0;
+    if (voe_wrapper_sc_->hw()->GetNumOfPlayoutDevices(num_of_devices) != -1 &&
+        num_of_devices > 0) {
+      if (voe_wrapper_sc_->hw()->SetPlayoutDevice(kDefaultSoundclipDeviceId)
+          == -1) {
+        LOG_RTCERR1_EX(SetPlayoutDevice, kDefaultSoundclipDeviceId,
+                       voe_wrapper_sc_->error());
+        return false;
+      }
+    } else {
+      LOG(LS_WARNING) << "No valid sound playout device found.";
     }
-  } else {
-    LOG(LS_WARNING) << "No valid sound playout device found.";
   }
 #endif
 
@@ -438,6 +453,12 @@
     LOG_RTCERR1(SetEcStatus, aec);
     return false;
   }
+  if (aec) {
+    if (voe_wrapper_->processing()->SetEcMetricsStatus(true) == -1) {
+      LOG_RTCERR1(SetEcMetricsStatus, true);
+      return false;
+    }
+  }
 
   if (voe_wrapper_->processing()->SetAgcStatus(agc) == -1) {
     LOG_RTCERR1(SetAgcStatus, agc);
@@ -480,7 +501,7 @@
   }
 
   // No typing detection support on iOS or Android.
-#endif // !IOS && !ANDROID
+#endif  // !IOS && !ANDROID
 
   return true;
 }
@@ -825,15 +846,16 @@
 
 // Ignore spammy trace messages, mostly from the stats API when we haven't
 // gotten RTCP info yet from the remote side.
-static bool ShouldIgnoreTrace(const std::string& trace) {
+bool WebRtcVoiceEngine::ShouldIgnoreTrace(const std::string& trace) {
   static const char* kTracesToIgnore[] = {
     "\tfailed to GetReportBlockInformation",
     "GetRecCodec() failed to get received codec",
-    "GetRemoteRTCPData() failed to measure statistics dueto lack of received RTP and/or RTCP packets",  // NOLINT
-    "GetRemoteRTCPData() failed to retrieve sender info for remoteside",
-    "GetRTPStatistics() failed to measure RTT since noRTP packets have been received yet",  // NOLINT
+    "GetReceivedRtcpStatistics: Could not get received RTP statistics",
+    "GetRemoteRTCPData() failed to measure statistics due to lack of received RTP and/or RTCP packets",  // NOLINT
+    "GetRemoteRTCPData() failed to retrieve sender info for remote side",
+    "GetRTPStatistics() failed to measure RTT since no RTP packets have been received yet",  // NOLINT
     "GetRTPStatistics() failed to read RTP statistics from the RTP/RTCP module",
-    "GetRTPStatistics() failed to retrieve RTT fromthe RTP/RTCP module",
+    "GetRTPStatistics() failed to retrieve RTT from the RTP/RTCP module",
     "webrtc::RTCPReceiver::SenderInfoReceived No received SR",
     "StatisticsRTP() no statisitics availble",
     NULL
@@ -865,7 +887,7 @@
     } else {
       std::string msg(trace + 71, length - 72);
       if (!ShouldIgnoreTrace(msg)) {
-        LOG_V(sev) << "WebRtc VoE:" << msg;
+        LOG_V(sev) << "WebRtc:" << msg;
       }
     }
   }
@@ -877,13 +899,13 @@
   WebRtcVoiceMediaChannel* channel = NULL;
   uint32 ssrc = 0;
   LOG(LS_WARNING) << "VoiceEngine error " << err_code << " reported on channel "
-               << channel_num << ".";
+                  << channel_num << ".";
   if (FindChannelAndSsrc(channel_num, &channel, &ssrc)) {
     ASSERT(channel != NULL);
     channel->OnError(ssrc, err_code);
   } else {
     LOG(LS_ERROR) << "VoiceEngine channel " << channel_num
-        << " could not be found in the channel list when error reported.";
+                  << " could not be found in channel list when error reported.";
   }
 }
 
@@ -906,6 +928,35 @@
   return false;
 }
 
+// This method will search through the WebRtcVoiceMediaChannels and
+// obtain the voice engine's channel number.
+bool WebRtcVoiceEngine::FindChannelNumFromSsrc(
+    uint32 ssrc, MediaProcessorDirection direction, int* channel_num) {
+  ASSERT(channel_num != NULL);
+
+  *channel_num = -1;
+  // Find corresponding channel for ssrc.
+  for (ChannelList::const_iterator it = channels_.begin();
+      it != channels_.end(); ++it) {
+    ASSERT(*it != NULL);
+    uint32 local_ssrc;
+    if ((direction & MPD_RX) != 0) {
+      *channel_num = (*it)->GetChannelNum(ssrc);
+    }
+    if (*channel_num == -1 &&
+        voe()->rtp()->GetLocalSSRC((*it)->voe_channel(), local_ssrc) != -1) {
+      if (ssrc == local_ssrc) {
+        *channel_num = (*it)->voe_channel();
+      }
+    }
+    if (*channel_num != -1) {
+      return true;
+    }
+  }
+  LOG(LS_WARNING) << "FindChannelFromSsrc. No Channel Found for Ssrc: " << ssrc;
+  return false;
+}
+
 void WebRtcVoiceEngine::RegisterChannel(WebRtcVoiceMediaChannel *channel) {
   talk_base::CritScope lock(&channels_cs_);
   channels_.push_back(channel);
@@ -1013,18 +1064,144 @@
   return true;
 }
 
+bool WebRtcVoiceEngine::RegisterProcessor(
+    uint32 ssrc,
+    VoiceProcessor* voice_processor,
+    MediaProcessorDirection direction) {
+  bool register_with_webrtc = false;
+  int channel_id = -1;
+  bool success = false;
+  bool found_channel = FindChannelNumFromSsrc(ssrc, direction, &channel_id);
+  if (voice_processor == NULL || !found_channel) {
+    LOG(LS_WARNING) << "Media Processing Registration Failed. ssrc: " << ssrc
+        << " foundChannel: " << found_channel;
+    return false;
+  }
+  talk_base::CritScope cs(&signal_media_critical_);
+  webrtc::ProcessingTypes processing_type;
+  if (direction == MPD_RX) {
+    processing_type = webrtc::kPlaybackPerChannel;
+    if (SignalRxMediaFrame.is_empty()) {
+      register_with_webrtc = true;
+    }
+    SignalRxMediaFrame.connect(voice_processor,
+                               &VoiceProcessor::OnFrame);
+  } else {
+    processing_type = webrtc::kRecordingPerChannel;
+    if (SignalTxMediaFrame.is_empty()) {
+      register_with_webrtc = true;
+    }
+    SignalTxMediaFrame.connect(voice_processor,
+                               &VoiceProcessor::OnFrame);
+  }
+  if (register_with_webrtc) {
+    if (voe()->media()->
+        RegisterExternalMediaProcessing(channel_id,
+                                        processing_type,
+                                        *this) == -1) {
+      LOG_RTCERR2(RegisterExternalMediaProcessing,
+                  channel_id,
+                  processing_type);
+      success = false;
+    } else {
+      LOG(LS_INFO) << "Media Processing Registration Succeeded. channel:"
+                   << channel_id;
+      success = true;
+    }
+  } else {
+    // If we don't have to register with the engine, we just needed to
+    // connect a new processor, set success to true;
+    success = true;
+  }
+  return success;
+}
+
+bool WebRtcVoiceEngine::UnregisterProcessor(
+    uint32 ssrc,
+    VoiceProcessor* voice_processor,
+    MediaProcessorDirection direction) {
+  int channel_id = -1;
+  bool found_channel = FindChannelNumFromSsrc(ssrc, direction, &channel_id);
+  bool success = true;
+  if (voice_processor == NULL || !found_channel) {
+    LOG(LS_WARNING) << "Media Processing Deregistration Failed. ssrc: "
+                    << ssrc
+                    << " foundChannel: "
+                    << found_channel;
+    return false;
+  }
+  talk_base::CritScope cs(&signal_media_critical_);
+  if ((direction & MPD_RX) != 0) {
+    SignalRxMediaFrame.disconnect(voice_processor);
+    if (SignalRxMediaFrame.is_empty()) {
+      if (voe()->media()->DeRegisterExternalMediaProcessing(channel_id,
+          webrtc::kPlaybackPerChannel) != -1) {
+        LOG(LS_INFO) << "Media Processing DeRegistration Succeeded. channel:"
+                     << channel_id;
+      } else {
+        LOG_RTCERR2(DeRegisterExternalMediaProcessing,
+                    channel_id,
+                    webrtc::kPlaybackPerChannel);
+        success = false;
+      }
+    }
+  }
+  if ((direction & MPD_TX) != 0) {
+    SignalTxMediaFrame.disconnect(voice_processor);
+    if (SignalTxMediaFrame.is_empty()) {
+      if (voe()->media()->DeRegisterExternalMediaProcessing(channel_id,
+          webrtc::kRecordingPerChannel) != -1) {
+        LOG(LS_INFO) << "Media Processing DeRegistration Succeeded. channel:"
+                     << channel_id;
+      } else {
+        LOG_RTCERR2(DeRegisterExternalMediaProcessing,
+                    channel_id,
+                    webrtc::kRecordingPerChannel);
+        success = false;
+      }
+    }
+  }
+  return success;
+}
+
+// Implementing method from WebRtc VoEMediaProcess interface
+void WebRtcVoiceEngine::Process(const int channel,
+                                const webrtc::ProcessingTypes type,
+                                WebRtc_Word16 audio10ms[],
+                                const int length,
+                                const int sampling_freq,
+                                const bool is_stereo) {
+  uint32 ssrc;
+  WebRtcVoiceMediaChannel* media_channel;
+
+  if (FindChannelAndSsrc(channel, &media_channel, &ssrc)) {
+    talk_base::CritScope cs(&signal_media_critical_);
+    AudioFrame frame(audio10ms, length, sampling_freq, is_stereo);
+    if (type == webrtc::kPlaybackPerChannel) {
+      SignalRxMediaFrame(ssrc, &frame);
+    } else if (type == webrtc::kRecordingPerChannel) {
+      SignalTxMediaFrame(ssrc, &frame);
+    }
+  } else {
+    LOG(LS_WARNING) << "MediaProcess Callback invoked with unexpected channel: "
+                    << channel;
+  }
+}
+
 // WebRtcVoiceMediaChannel
 WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine)
     : WebRtcMediaChannel<VoiceMediaChannel, WebRtcVoiceEngine>(
           engine,
           engine->voe()->base()->CreateChannel()),
+      recv_codecs_set_(false),
       channel_options_(0),
       agc_adjusted_(false),
       dtmf_allowed_(false),
       desired_playout_(false),
       playout_(false),
       desired_send_(SEND_NOTHING),
-      send_(SEND_NOTHING) {
+      send_(SEND_NOTHING),
+      local_ssrc_(0) {
   engine->RegisterChannel(this);
   LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel "
                   << voe_channel();
@@ -1038,9 +1215,6 @@
   // Enable RTCP (for quality stats and feedback messages)
   EnableRtcp(voe_channel());
 
-  // Create a random but nonzero send SSRC
-  SetSendSsrc(talk_base::CreateRandomNonZeroId());
-
   // Reset all recv codecs; they will be enabled via SetRecvCodecs.
   ResetRecvCodecs(voe_channel());
 }
@@ -1059,8 +1233,9 @@
   engine()->UnregisterChannel(this);
   // Remove any remaining streams.
   while (!mux_channels_.empty()) {
-    RemoveStream(mux_channels_.begin()->first);
+    RemoveRecvStream(mux_channels_.begin()->first);
   }
+
   // Delete the primary channel.
   if (engine()->voe()->base()->DeleteChannel(voe_channel()) == -1) {
     LOG_RTCERR1(DeleteChannel, voe_channel());
@@ -1099,12 +1274,21 @@
         LOG_RTCERR2(SetRecPayloadType, voe_channel(), ToString(voe_codec));
         ret = false;
       }
+      // Set the receive codecs on all receiving channels.
+      for (ChannelMap::iterator it = mux_channels_.begin();
+           it != mux_channels_.end() && ret; ++it) {
+        if (engine()->voe()->codec()->SetRecPayloadType(
+            it->second, voe_codec) == -1) {
+          LOG_RTCERR2(SetRecPayloadType, it->second, ToString(voe_codec));
+          ret = false;
+        }
+      }
     } else {
       LOG(LS_WARNING) << "Unknown codec " << ToString(*it);
       ret = false;
     }
   }
-
+  recv_codecs_set_ = ret;
   return ret;
 }
 
@@ -1210,6 +1394,7 @@
     return false;
   }
 
+  send_codec_.reset(new webrtc::CodecInst(send_codec));
   return true;
 }
 
@@ -1293,7 +1478,9 @@
 
 bool WebRtcVoiceMediaChannel::SetSend(SendFlags send) {
   desired_send_ = send;
-  return ChangeSend(desired_send_);
+  if (local_ssrc_ != 0)
+    return ChangeSend(desired_send_);
+  return true;
 }
 
 bool WebRtcVoiceMediaChannel::PauseSend() {
@@ -1325,6 +1512,11 @@
     }
 #endif  // CHROMEOS
 
+    if ((channel_options_ & OPT_AGC_MINUS_10DB) && !agc_adjusted_) {
+      if (engine()->AdjustAgcLevel(kMinus10DbAdjustment)) {
+        agc_adjusted_ = true;
+      }
+    }
 
     // VoiceEngine resets sequence number when StopSend is called. This
     // sometimes causes libSRTP to complain about packets being
@@ -1399,9 +1591,65 @@
   return true;
 }
 
-bool WebRtcVoiceMediaChannel::AddStream(uint32 ssrc) {
+bool WebRtcVoiceMediaChannel::AddSendStream(const StreamParams& sp) {
+  if (local_ssrc_ != 0) {
+    LOG(LS_ERROR) << "WebRtcVoiceMediaChannel supports one sending channel.";
+    return false;
+  }
+
+  if (engine()->voe()->rtp()->SetLocalSSRC(voe_channel(), sp.first_ssrc())
+        == -1) {
+    LOG_RTCERR2(SetSendSSRC, voe_channel(), sp.first_ssrc());
+    return false;
+  }
+  // Set the SSRC on the receive channels.
+  // Receive channels have to have the same SSRC in order to send receiver
+  // reports with this SSRC.
+  for (ChannelMap::const_iterator it = mux_channels_.begin();
+       it != mux_channels_.end(); ++it) {
+    int channel_id = it->second;
+    if (engine()->voe()->rtp()->SetLocalSSRC(channel_id,
+                                             sp.first_ssrc()) != 0) {
+      LOG_RTCERR1(SetLocalSSRC, it->first);
+      return false;
+    }
+  }
+
+  if (engine()->voe()->rtp()->SetRTCP_CNAME(voe_channel(),
+                                            sp.cname.c_str()) == -1) {
+     LOG_RTCERR2(SetRTCP_CNAME, voe_channel(), sp.cname);
+     return false;
+  }
+
+  local_ssrc_ = sp.first_ssrc();
+  if (desired_send_ != send_)
+    return ChangeSend(desired_send_);
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::RemoveSendStream(uint32 ssrc) {
+  if (ssrc != local_ssrc_) {
+    return false;
+  }
+  local_ssrc_ = 0;
+  ChangeSend(SEND_NOTHING);
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) {
   talk_base::CritScope lock(&mux_channels_cs_);
 
+  // Reuse default channel for recv stream in 1:1 call.
+  if ((channel_options_ & OPT_CONFERENCE) == 0) {
+    LOG(LS_INFO) << "Recv stream " << sp.first_ssrc()
+                 << " reuse default channel";
+    return true;
+  }
+
+  if (!VERIFY(sp.ssrcs.size() == 1))
+    return false;
+  uint32 ssrc = sp.first_ssrc();
+
   if (mux_channels_.find(ssrc) != mux_channels_.end()) {
     return false;
   }
@@ -1434,17 +1682,19 @@
 
   // Use the same recv payload types as our default channel.
   ResetRecvCodecs(channel);
-  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) != -1) {
-      voe_codec.rate = 0;  // Needed to make GetRecPayloadType work for ISAC
-      if (engine()->voe()->codec()->GetRecPayloadType(
-          voe_channel(), voe_codec) != -1) {
-        if (engine()->voe()->codec()->SetRecPayloadType(
-            channel, voe_codec) == -1) {
-          LOG_RTCERR2(SetRecPayloadType, channel, ToString(voe_codec));
-          return false;
+  if (recv_codecs_set_) {
+    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) != -1) {
+        voe_codec.rate = 0;  // Needed to make GetRecPayloadType work for ISAC
+        if (engine()->voe()->codec()->GetRecPayloadType(
+            voe_channel(), voe_codec) != -1) {
+          if (engine()->voe()->codec()->SetRecPayloadType(
+              channel, voe_codec) == -1) {
+            LOG_RTCERR2(SetRecPayloadType, channel, ToString(voe_codec));
+            return false;
+          }
         }
       }
     }
@@ -1470,7 +1720,7 @@
   return SetPlayout(channel, playout_);
 }
 
-bool WebRtcVoiceMediaChannel::RemoveStream(uint32 ssrc) {
+bool WebRtcVoiceMediaChannel::RemoveRecvStream(uint32 ssrc) {
   talk_base::CritScope lock(&mux_channels_cs_);
   ChannelMap::iterator it = mux_channels_.find(ssrc);
 
@@ -1538,7 +1788,7 @@
       channels.push_back(it->second);
     }
   } else {  // Collect only the channel of the specified ssrc.
-    int channel = GetChannel(ssrc);
+    int channel = GetChannelNum(ssrc);
     if (-1 == channel) {
       LOG(LS_WARNING) << "Cannot find channel for ssrc:" << ssrc;
       return false;
@@ -1579,7 +1829,7 @@
 
   talk_base::CritScope lock(&mux_channels_cs_);
   // Determine which channel based on ssrc.
-  int channel = (0 == ssrc) ? voe_channel() : GetChannel(ssrc);
+  int channel = (0 == ssrc) ? voe_channel() : GetChannelNum(ssrc);
   if (channel == -1) {
     LOG(LS_WARNING) << "Cannot find channel for ssrc:" << ssrc;
     return false;
@@ -1619,7 +1869,7 @@
   }
 
   // Determine which VoiceEngine channel to play on.
-  int channel = (ssrc == 0) ? voe_channel() : GetChannel(ssrc);
+  int channel = (ssrc == 0) ? voe_channel() : GetChannelNum(ssrc);
   if (channel == -1) {
     return false;
   }
@@ -1637,8 +1887,8 @@
     ringback_channels_.insert(channel);
     LOG(LS_INFO) << "Started ringback on channel " << channel;
   } else {
-    if (engine()->voe()->file()->StopPlayingFileLocally(channel)
-        == -1) {
+    if (engine()->voe()->file()->IsPlayingFileLocally(channel) == 1 &&
+        engine()->voe()->file()->StopPlayingFileLocally(channel) == -1) {
       LOG_RTCERR1(StopPlayingFileLocally, channel);
       return false;
     }
@@ -1675,7 +1925,7 @@
   // Pick which channel to send this packet to. If this packet doesn't match
   // any multiplexed streams, just send it to the default channel. Otherwise,
   // send it to the specific decoder instance for that stream.
-  int which_channel = GetChannel(
+  int which_channel = GetChannelNum(
       ParseSsrc(packet->data(), packet->length(), false));
   if (which_channel == -1) {
     which_channel = voe_channel();
@@ -1703,7 +1953,7 @@
 
 void WebRtcVoiceMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
   // See above.
-  int which_channel = GetChannel(
+  int which_channel = GetChannelNum(
       ParseSsrc(packet->data(), packet->length(), true));
   if (which_channel == -1) {
     which_channel = voe_channel();
@@ -1714,22 +1964,6 @@
                                                     packet->length());
 }
 
-void WebRtcVoiceMediaChannel::SetSendSsrc(uint32 ssrc) {
-  if (engine()->voe()->rtp()->SetLocalSSRC(voe_channel(), ssrc)
-      == -1) {
-     LOG_RTCERR2(SetSendSSRC, voe_channel(), ssrc);
-  }
-}
-
-bool WebRtcVoiceMediaChannel::SetRtcpCName(const std::string& cname) {
-  if (engine()->voe()->rtp()->SetRTCP_CNAME(voe_channel(),
-                                                    cname.c_str()) == -1) {
-     LOG_RTCERR2(SetRTCP_CNAME, voe_channel(), cname);
-     return false;
-  }
-  return true;
-}
-
 bool WebRtcVoiceMediaChannel::Mute(bool muted) {
   if (engine()->voe()->volume()->SetInputMute(voe_channel(),
       muted) == -1) {
@@ -1752,7 +1986,6 @@
   // Fill in the sender info, based on what we know, and what the
   // remote side told us it got from its RTCP report.
   VoiceSenderInfo sinfo;
-  memset(&sinfo, 0, sizeof(sinfo));
 
   // Data we obtain locally.
   memset(&cs, 0, sizeof(cs));
@@ -1762,6 +1995,7 @@
   }
 
   sinfo.ssrc = ssrc;
+  sinfo.codec_name = send_codec_.get() ? send_codec_->plname : "";
   sinfo.bytes_sent = cs.bytesSent;
   sinfo.packets_sent = cs.packetsSent;
   // RTT isn't known until a RTCP report is received. Until then, VoiceEngine
@@ -1792,6 +2026,35 @@
   // Local speech level.
   sinfo.audio_level = (engine()->voe()->volume()->
       GetSpeechInputLevelFullRange(level) != -1) ? level : -1;
+
+  bool echo_metrics_on = false;
+  // These can take on valid negative values, so use the lowest possible level
+  // as default rather than -1.
+  sinfo.echo_return_loss = -100;
+  sinfo.echo_return_loss_enhancement = -100;
+  // These can also be negative, but in practice -1 is only used to signal
+  // insufficient data, since the resolution is limited to multiples of 4 ms.
+  sinfo.echo_delay_median_ms = -1;
+  sinfo.echo_delay_std_ms = -1;
+  if (engine()->voe()->processing()->GetEcMetricsStatus(echo_metrics_on) !=
+      -1 && echo_metrics_on) {
+    // TODO: we may want to use VoECallReport::GetEchoMetricsSummary
+    // here, but it appears to be unsuitable currently. Revisit after this is
+    // investigated: http://b/issue?id=5666755
+    int erl, erle, rerl, anlp;
+    if (engine()->voe()->processing()->GetEchoMetrics(erl, erle, rerl, anlp) !=
+        -1) {
+      sinfo.echo_return_loss = erl;
+      sinfo.echo_return_loss_enhancement = erle;
+    }
+
+    int median, std;
+    if (engine()->voe()->processing()->GetEcDelayMetrics(median, std) != -1) {
+      sinfo.echo_delay_median_ms = median;
+      sinfo.echo_delay_std_ms = std;
+    }
+  }
+
   info->senders.push_back(sinfo);
 
   // Build the list of receivers, one for each mux channel, or 1 in a 1:1 call.
@@ -1812,7 +2075,6 @@
         engine()->voe()->rtp()->GetRTCPStatistics(*it, cs) != -1 &&
         engine()->voe()->codec()->GetRecCodec(*it, codec) != -1) {
       VoiceReceiverInfo rinfo;
-      memset(&rinfo, 0, sizeof(rinfo));
       rinfo.ssrc = ssrc;
       rinfo.bytes_rcvd = cs.bytesReceived;
       rinfo.packets_rcvd = cs.packetsReceived;
@@ -1899,7 +2161,7 @@
   return (ret == 0) ? static_cast<int>(ulevel) : -1;
 }
 
-int WebRtcVoiceMediaChannel::GetChannel(uint32 ssrc) {
+int WebRtcVoiceMediaChannel::GetChannelNum(uint32 ssrc) {
   ChannelMap::iterator it = mux_channels_.find(ssrc);
   return (it != mux_channels_.end()) ? it->second : -1;
 }
diff --git a/talk/session/phone/webrtcvoiceengine.h b/talk/session/phone/webrtcvoiceengine.h
index 66c9785..7cc148a 100644
--- a/talk/session/phone/webrtcvoiceengine.h
+++ b/talk/session/phone/webrtcvoiceengine.h
@@ -41,6 +41,7 @@
 #include "talk/session/phone/channel.h"
 #include "talk/session/phone/rtputils.h"
 #include "talk/session/phone/webrtccommon.h"
+#include "talk/session/phone/webrtcvoe.h"
 #ifdef WEBRTC_RELATIVE_PATH
 #include "voice_engine/main/interface/voe_base.h"
 #else
@@ -76,6 +77,7 @@
 class AudioDeviceModule;
 class VoETraceWrapper;
 class VoEWrapper;
+class VoiceProcessor;
 class WebRtcSoundclipMedia;
 class WebRtcVoiceMediaChannel;
 
@@ -83,7 +85,8 @@
 // It uses the WebRtc VoiceEngine library for audio handling.
 class WebRtcVoiceEngine
     : public webrtc::VoiceEngineObserver,
-      public webrtc::TraceCallback {
+      public webrtc::TraceCallback,
+      public webrtc::VoEMediaProcess  {
  public:
   WebRtcVoiceEngine();
   // Dependency injection for testing.
@@ -112,6 +115,21 @@
 
   void SetLogging(int min_sev, const char* filter);
 
+  bool RegisterProcessor(uint32 ssrc,
+                         VoiceProcessor* voice_processor,
+                         MediaProcessorDirection direction);
+  bool UnregisterProcessor(uint32 ssrc,
+                           VoiceProcessor* voice_processor,
+                           MediaProcessorDirection direction);
+
+  // Method from webrtc::VoEMediaProcess
+  virtual void Process(const int channel,
+                       const webrtc::ProcessingTypes type,
+                       WebRtc_Word16 audio10ms[],
+                       const int length,
+                       const int sampling_freq,
+                       const bool is_stereo);
+
   // For tracking WebRtc channels. Needed because we have to pause them
   // all when switching devices.
   // May only be called by WebRtcVoiceMediaChannel.
@@ -138,6 +156,9 @@
   bool SetAudioDeviceModule(webrtc::AudioDeviceModule* adm,
                             webrtc::AudioDeviceModule* adm_sc);
 
+  // Check whether the supplied trace should be ignored.
+  bool ShouldIgnoreTrace(const std::string& trace);
+
  private:
   typedef std::vector<WebRtcSoundclipMedia *> SoundclipList;
   typedef std::vector<WebRtcVoiceMediaChannel *> ChannelList;
@@ -162,10 +183,19 @@
   bool FindChannelAndSsrc(int channel_num,
                           WebRtcVoiceMediaChannel** channel,
                           uint32* ssrc) const;
+  bool FindChannelNumFromSsrc(uint32 ssrc,
+                              MediaProcessorDirection direction,
+                              int* channel_num);
   bool ChangeLocalMonitor(bool enable);
   bool PauseLocalMonitor();
   bool ResumeLocalMonitor();
 
+  // When a voice processor registers with the engine, it is connected
+  // to either the Rx or Tx signals, based on the direction parameter.
+  // SignalXXMediaFrame will be invoked for every audio packet.
+  sigslot::signal2<uint32, AudioFrame*> SignalRxMediaFrame;
+  sigslot::signal2<uint32, AudioFrame*> SignalTxMediaFrame;
+
   static const int kDefaultLogSeverity = talk_base::LS_WARNING;
   static const CodecPref kCodecPrefs[];
 
@@ -190,6 +220,8 @@
   talk_base::CriticalSection channels_cs_;
   webrtc::AgcConfig default_agc_config_;
   bool initialized_;
+
+  talk_base::CriticalSection signal_media_critical_;
 };
 
 // WebRtcMediaChannel is a class that implements the common WebRtc channel
@@ -247,8 +279,7 @@
 // WebRtcVoiceMediaChannel is an implementation of VoiceMediaChannel that uses
 // WebRtc Voice Engine.
 class WebRtcVoiceMediaChannel
-    : public WebRtcMediaChannel<VoiceMediaChannel,
-                                WebRtcVoiceEngine> {
+    : public WebRtcMediaChannel<VoiceMediaChannel, WebRtcVoiceEngine> {
  public:
   explicit WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine);
   virtual ~WebRtcVoiceMediaChannel();
@@ -265,8 +296,10 @@
   virtual bool SetSend(SendFlags send);
   bool PauseSend();
   bool ResumeSend();
-  virtual bool AddStream(uint32 ssrc);
-  virtual bool RemoveStream(uint32 ssrc);
+  virtual bool AddSendStream(const StreamParams& sp);
+  virtual bool RemoveSendStream(uint32 ssrc);
+  virtual bool AddRecvStream(const StreamParams& sp);
+  virtual bool RemoveRecvStream(uint32 ssrc);
   virtual bool GetActiveStreams(AudioInfo::StreamList* actives);
   virtual int GetOutputLevel();
   virtual bool SetOutputScaling(uint32 ssrc, double left, double right);
@@ -278,8 +311,6 @@
 
   virtual void OnPacketReceived(talk_base::Buffer* packet);
   virtual void OnRtcpReceived(talk_base::Buffer* packet);
-  virtual void SetSendSsrc(uint32 id);
-  virtual bool SetRtcpCName(const std::string& cname);
   virtual bool Mute(bool mute);
   virtual bool SetSendBandwidth(bool autobw, int bps) { return false; }
   virtual bool GetStats(VoiceMediaInfo* info);
@@ -290,9 +321,11 @@
   bool FindSsrc(int channel_num, uint32* ssrc);
   void OnError(uint32 ssrc, int error);
 
+  bool sending() const { return send_ != SEND_NOTHING; }
+  int GetChannelNum(uint32 ssrc);
+
  protected:
   int GetLastEngineError() { return engine()->GetLastEngineError(); }
-  int GetChannel(uint32 ssrc);
   int GetOutputLevel(int channel);
   bool GetRedSendCodec(const AudioCodec& red_codec,
                        const std::vector<AudioCodec>& all_codecs,
@@ -304,9 +337,9 @@
   static Error WebRtcErrorToChannelError(int err_code);
 
  private:
-  // Tandberg-bridged conferences require a -10dB gain adjustment,
-  // which is actually +10 in AgcConfig.targetLeveldBOv
-  static const int kTandbergDbAdjustment = 10;
+  // A -10dB gain adjustment is actually +10 in
+  // AgcConfig.targetLeveldBOv
+  static const int kMinus10DbAdjustment = 10;
 
   bool ChangePlayout(bool playout);
   bool ChangeSend(SendFlags send);
@@ -314,6 +347,8 @@
   typedef std::map<uint32, int> ChannelMap;
   talk_base::scoped_ptr<WebRtcSoundclipStream> ringback_tone_;
   std::set<int> ringback_channels_;  // channels playing ringback
+  bool recv_codecs_set_;
+  talk_base::scoped_ptr<webrtc::CodecInst> send_codec_;
   int channel_options_;
   bool agc_adjusted_;
   bool dtmf_allowed_;
@@ -321,12 +356,15 @@
   bool playout_;
   SendFlags desired_send_;
   SendFlags send_;
+
+  uint32 local_ssrc_;
   ChannelMap mux_channels_;  // for multiple sources
   // mux_channels_ can be read from WebRtc callback thread.  Accesses off the
   // WebRtc thread must be synchronized with edits on the worker thread.  Reads
   // on the worker thread are ok.
   mutable talk_base::CriticalSection mux_channels_cs_;
 };
-}
+
+}  // namespace cricket
 
 #endif  // TALK_SESSION_PHONE_WEBRTCVOICEENGINE_H_
diff --git a/talk/session/phone/webrtcvoiceengine_unittest.cc b/talk/session/phone/webrtcvoiceengine_unittest.cc
index f4e437f..f971106 100644
--- a/talk/session/phone/webrtcvoiceengine_unittest.cc
+++ b/talk/session/phone/webrtcvoiceengine_unittest.cc
@@ -4,10 +4,11 @@
 
 #include "talk/base/byteorder.h"
 #include "talk/base/gunit.h"
+#include "talk/p2p/base/fakesession.h"
 #include "talk/session/phone/channel.h"
 #include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/fakemediaprocessor.h"
 #include "talk/session/phone/fakertp.h"
-#include "talk/session/phone/fakesession.h"
 #include "talk/session/phone/fakewebrtcvoiceengine.h"
 #include "talk/session/phone/webrtcvoiceengine.h"
 
@@ -25,6 +26,7 @@
     &kTelephoneEventCodec,
 };
 const char kRingbackTone[] = "RIFF____WAVE____ABCD1234";
+static uint32 kSsrc1 = 0x99;
 
 class FakeVoEWrapper : public cricket::VoEWrapper {
  public:
@@ -57,7 +59,7 @@
   }
 };
 
-class WebRtcVoiceEngineTest : public testing::Test {
+class WebRtcVoiceEngineTestFake : public testing::Test {
  public:
   class ChannelErrorListener : public sigslot::has_slots<> {
    public:
@@ -88,7 +90,7 @@
     cricket::VoiceMediaChannel::Error error_;
   };
 
-  WebRtcVoiceEngineTest()
+  WebRtcVoiceEngineTestFake()
       : voe_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
         voe_sc_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
         engine_(new FakeVoEWrapper(&voe_),
@@ -102,6 +104,10 @@
       channel_ = engine_.CreateChannel();
       result = (channel_ != NULL);
     }
+    if (result) {
+      result = channel_->AddSendStream(
+          cricket::StreamParams::CreateLegacy(kSsrc1));
+    }
     return result;
   }
   void DeliverPacket(const void* data, int len) {
@@ -123,7 +129,7 @@
 };
 
 // Tests that our stub library "works".
-TEST_F(WebRtcVoiceEngineTest, StartupShutdown) {
+TEST_F(WebRtcVoiceEngineTestFake, StartupShutdown) {
   EXPECT_FALSE(voe_.IsInited());
   EXPECT_FALSE(voe_sc_.IsInited());
   EXPECT_TRUE(engine_.Init());
@@ -135,14 +141,14 @@
 }
 
 // Tests that we can create and destroy a channel.
-TEST_F(WebRtcVoiceEngineTest, CreateChannel) {
+TEST_F(WebRtcVoiceEngineTestFake, CreateChannel) {
   EXPECT_TRUE(engine_.Init());
   channel_ = engine_.CreateChannel();
   EXPECT_TRUE(channel_ != NULL);
 }
 
 // Tests that we properly handle failures in CreateChannel.
-TEST_F(WebRtcVoiceEngineTest, CreateChannelFail) {
+TEST_F(WebRtcVoiceEngineTestFake, CreateChannelFail) {
   voe_.set_fail_create_channel(true);
   EXPECT_TRUE(engine_.Init());
   channel_ = engine_.CreateChannel();
@@ -151,7 +157,7 @@
 
 // Tests that the list of supported codecs is created properly and ordered
 // correctly
-TEST_F(WebRtcVoiceEngineTest, CodecPreference) {
+TEST_F(WebRtcVoiceEngineTestFake, CodecPreference) {
   const std::vector<cricket::AudioCodec>& codecs = engine_.codecs();
   ASSERT_FALSE(codecs.empty());
   EXPECT_EQ("ISAC", codecs[0].name);
@@ -166,7 +172,7 @@
 
 // Tests that we can find codecs by name or id, and that we interpret the
 // clockrate and bitrate fields properly.
-TEST_F(WebRtcVoiceEngineTest, FindCodec) {
+TEST_F(WebRtcVoiceEngineTestFake, FindCodec) {
   cricket::AudioCodec codec;
   webrtc::CodecInst codec_inst;
   // Find PCMU with explicit clockrate and bitrate.
@@ -201,7 +207,7 @@
 }
 
 // Test that we set our inbound codecs properly, including changing PT.
-TEST_F(WebRtcVoiceEngineTest, SetRecvCodecs) {
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecs) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -226,7 +232,7 @@
 }
 
 // Test that we fail to set an unknown inbound codec.
-TEST_F(WebRtcVoiceEngineTest, SetRecvCodecsUnsupportedCodec) {
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsUnsupportedCodec) {
   EXPECT_TRUE(SetupEngine());
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kIsacCodec);
@@ -235,7 +241,7 @@
 }
 
 // Test that we fail if we have duplicate types in the inbound list.
-TEST_F(WebRtcVoiceEngineTest, SetRecvCodecsDuplicatePayloadType) {
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsDuplicatePayloadType) {
   EXPECT_TRUE(SetupEngine());
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kIsacCodec);
@@ -245,8 +251,9 @@
 }
 
 // Test that changes to recv codecs are applied to all streams.
-TEST_F(WebRtcVoiceEngineTest, SetRecvCodecsWithMultipleStreams) {
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsWithMultipleStreams) {
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kIsacCodec);
   codecs.push_back(kPcmuCodec);
@@ -254,7 +261,8 @@
   codecs[0].id = 106;  // collide with existing telephone-event
   codecs[2].id = 126;
   EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
-  EXPECT_TRUE(channel_->AddStream(2));
+  EXPECT_TRUE(channel_->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(kSsrc1)));
   int channel_num2 = voe_.GetLastChannel();
   webrtc::CodecInst gcodec;
   talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname), "ISAC");
@@ -270,8 +278,28 @@
   EXPECT_STREQ("telephone-event", gcodec.plname);
 }
 
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsAfterAddingStreams) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs[0].id = 106;  // collide with existing telephone-event
+
+  EXPECT_TRUE(channel_->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(kSsrc1)));
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+
+  int channel_num2 = voe_.GetLastChannel();
+  webrtc::CodecInst gcodec;
+  talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname), "ISAC");
+  gcodec.plfreq = 16000;
+  EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num2, gcodec));
+  EXPECT_EQ(106, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+}
+
 // Test that we apply codecs properly.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecs) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecs) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -292,7 +320,7 @@
 }
 
 // Test that we handle various ways of specifying bitrate.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBitrate) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBitrate) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -330,7 +358,7 @@
 }
 
 // Test that we fall back to PCMU if no codecs are specified.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsNoCodecs) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsNoCodecs) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -347,7 +375,7 @@
 }
 
 // Test that we set VAD and DTMF types correctly.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsCNandDTMF) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCNandDTMF) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -374,7 +402,7 @@
 }
 
 // Test that we perform case-insensitive matching of codec names.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsCaseInsensitive) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCaseInsensitive) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -401,7 +429,7 @@
 }
 
 // Test that we set up FEC correctly.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsRED) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsRED) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -421,7 +449,7 @@
 }
 
 // Test that we set up FEC correctly if params are omitted.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsREDNoParams) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsREDNoParams) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -440,7 +468,7 @@
 }
 
 // Test that we ignore RED if the parameters aren't named the way we expect.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED1) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED1) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -459,7 +487,7 @@
 }
 
 // Test that we ignore RED if it uses different primary/secondary encoding.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED2) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED2) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -478,7 +506,7 @@
 }
 
 // Test that we ignore RED if it uses more than 2 encodings.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED3) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED3) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -497,7 +525,7 @@
 }
 
 // Test that we ignore RED if it has bogus codec ids.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED4) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED4) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -516,7 +544,7 @@
 }
 
 // Test that we ignore RED if it refers to a codec that is not present.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED5) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED5) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -535,7 +563,7 @@
 }
 
 // Test that we support setting an empty list of recv header extensions.
-TEST_F(WebRtcVoiceEngineTest, SetRecvRtpHeaderExtensions) {
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvRtpHeaderExtensions) {
   EXPECT_TRUE(SetupEngine());
   std::vector<cricket::RtpHeaderExtension> extensions;
   int channel_num = voe_.GetLastChannel();
@@ -558,7 +586,7 @@
 }
 
 // Test that we support setting certain send header extensions.
-TEST_F(WebRtcVoiceEngineTest, SetSendRtpHeaderExtensions) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendRtpHeaderExtensions) {
   EXPECT_TRUE(SetupEngine());
   std::vector<cricket::RtpHeaderExtension> extensions;
   int channel_num = voe_.GetLastChannel();
@@ -594,7 +622,7 @@
 }
 
 // Test that we can create a channel and start sending/playing out on it.
-TEST_F(WebRtcVoiceEngineTest, SendAndPlayout) {
+TEST_F(WebRtcVoiceEngineTestFake, SendAndPlayout) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -612,16 +640,17 @@
 
 // Test that we can add and remove streams, and do proper send/playout.
 // We can receive on multiple streams, but will only send on one.
-TEST_F(WebRtcVoiceEngineTest, SendAndPlayoutWithMultipleStreams) {
+TEST_F(WebRtcVoiceEngineTestFake, SendAndPlayoutWithMultipleStreams) {
   EXPECT_TRUE(SetupEngine());
   int channel_num1 = voe_.GetLastChannel();
 
   // Start playout on the default channel.
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   EXPECT_TRUE(channel_->SetPlayout(true));
   EXPECT_TRUE(voe_.GetPlayout(channel_num1));
 
   // Adding another stream should disable playout on the default channel.
-  EXPECT_TRUE(channel_->AddStream(2));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
   int channel_num2 = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kPcmuCodec);
@@ -635,7 +664,7 @@
   EXPECT_TRUE(voe_.GetPlayout(channel_num2));
 
   // Adding yet another stream should have stream 2 and 3 enabled for playout.
-  EXPECT_TRUE(channel_->AddStream(3));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(3)));
   int channel_num3 = voe_.GetLastChannel();
   EXPECT_FALSE(voe_.GetPlayout(channel_num1));
   EXPECT_TRUE(voe_.GetPlayout(channel_num2));
@@ -662,14 +691,14 @@
 
   // Now remove the new streams and verify that the default channel is
   // played out again.
-  EXPECT_TRUE(channel_->RemoveStream(3));
-  EXPECT_TRUE(channel_->RemoveStream(2));
+  EXPECT_TRUE(channel_->RemoveRecvStream(3));
+  EXPECT_TRUE(channel_->RemoveRecvStream(2));
 
   EXPECT_TRUE(voe_.GetPlayout(channel_num1));
 }
 
 // Test that we can set the devices to use.
-TEST_F(WebRtcVoiceEngineTest, SetDevices) {
+TEST_F(WebRtcVoiceEngineTestFake, SetDevices) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -723,7 +752,7 @@
 
 // Test that we can set the devices to use even if we failed to
 // open the initial ones.
-TEST_F(WebRtcVoiceEngineTest, SetDevicesWithInitiallyBadDevices) {
+TEST_F(WebRtcVoiceEngineTestFake, SetDevicesWithInitiallyBadDevices) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   std::vector<cricket::AudioCodec> codecs;
@@ -763,7 +792,7 @@
 
 // Test that we can create a channel configured for multi-point conferences,
 // and start sending/playing out on it.
-TEST_F(WebRtcVoiceEngineTest, ConferenceSendAndPlayout) {
+TEST_F(WebRtcVoiceEngineTestFake, ConferenceSendAndPlayout) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
@@ -806,13 +835,13 @@
 
 // Test that we can create a channel configured for Codian bridges,
 // and start sending/playing out on it.
-TEST_F(WebRtcVoiceEngineTest, CodianSendAndPlayout) {
+TEST_F(WebRtcVoiceEngineTestFake, CodianSendAndPlayout) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   webrtc::AgcConfig agc_config;
   EXPECT_EQ(0, voe_.GetAgcConfig(agc_config));
   EXPECT_EQ(0, agc_config.targetLeveldBOv);
-  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_AGC_TANDBERG_LEVELS));
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_AGC_MINUS_10DB));
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kPcmuCodec);
   EXPECT_TRUE(channel_->SetSendCodecs(codecs));
@@ -831,48 +860,72 @@
 }
 
 // Test that we can set the outgoing SSRC properly.
-TEST_F(WebRtcVoiceEngineTest, SetSendSsrc) {
+// SSRC is set in SetupEngine by calling AddSendStream.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrc) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   unsigned int send_ssrc;
   EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num, send_ssrc));
   EXPECT_NE(0U, send_ssrc);
-  channel_->SetSendSsrc(0x99);
   EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num, send_ssrc));
-  EXPECT_EQ(0x99U, send_ssrc);
+  EXPECT_EQ(kSsrc1, send_ssrc);
 }
 
 // Test that we can set the outgoing SSRC properly with multiple streams.
-TEST_F(WebRtcVoiceEngineTest, SetSendSsrcWithMultipleStreams) {
+// SSRC is set in SetupEngine by calling AddSendStream.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrcWithMultipleStreams) {
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   int channel_num1 = voe_.GetLastChannel();
   unsigned int send_ssrc;
-  channel_->SetSendSsrc(0x99);
   EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num1, send_ssrc));
-  EXPECT_EQ(0x99U, send_ssrc);
-  EXPECT_TRUE(channel_->AddStream(2));
+  EXPECT_EQ(kSsrc1, send_ssrc);
+
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
   int channel_num2 = voe_.GetLastChannel();
   EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num2, send_ssrc));
-  EXPECT_EQ(0x99U, send_ssrc);
+  EXPECT_EQ(kSsrc1, send_ssrc);
+}
+
+// Test that the local SSRC is the same on sending and receiving channels if the
+// receive channel is created before the send channel.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrcAfterCreatingReceiveChannel) {
+  EXPECT_TRUE(engine_.Init());
+  channel_ = engine_.CreateChannel();
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  int receive_channel_num = voe_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1234)));
+  int send_channel_num = voe_.GetLastChannel();
+
+  unsigned int ssrc = 0;
+  EXPECT_EQ(0, voe_.GetLocalSSRC(send_channel_num, ssrc));
+  EXPECT_EQ(1234U, ssrc);
+  ssrc = 0;
+  EXPECT_EQ(0, voe_.GetLocalSSRC(receive_channel_num, ssrc));
+  EXPECT_EQ(1234U, ssrc);
 }
 
 // Test that we can properly receive packets.
-TEST_F(WebRtcVoiceEngineTest, Recv) {
+TEST_F(WebRtcVoiceEngineTestFake, Recv) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
   EXPECT_TRUE(voe_.CheckPacket(channel_num, kPcmuFrame,
-                                      sizeof(kPcmuFrame)));
+                               sizeof(kPcmuFrame)));
 }
 
 // Test that we can properly receive packets on multiple streams.
-TEST_F(WebRtcVoiceEngineTest, RecvWithMultipleStreams) {
+TEST_F(WebRtcVoiceEngineTestFake, RecvWithMultipleStreams) {
   EXPECT_TRUE(SetupEngine());
-  EXPECT_TRUE(channel_->AddStream(1));
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
   int channel_num1 = voe_.GetLastChannel();
-  EXPECT_TRUE(channel_->AddStream(2));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
   int channel_num2 = voe_.GetLastChannel();
-  EXPECT_TRUE(channel_->AddStream(3));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(3)));
   int channel_num3 = voe_.GetLastChannel();
   // Create packets with the right SSRCs.
   char packets[4][sizeof(kPcmuFrame)];
@@ -889,37 +942,51 @@
   EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
   DeliverPacket(packets[1], sizeof(packets[1]));
   EXPECT_TRUE(voe_.CheckPacket(channel_num1, packets[1],
-                                      sizeof(packets[1])));
+                               sizeof(packets[1])));
   EXPECT_TRUE(voe_.CheckNoPacket(channel_num2));
   EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
   DeliverPacket(packets[2], sizeof(packets[2]));
   EXPECT_TRUE(voe_.CheckNoPacket(channel_num1));
   EXPECT_TRUE(voe_.CheckPacket(channel_num2, packets[2],
-                                      sizeof(packets[2])));
+                               sizeof(packets[2])));
   EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
   DeliverPacket(packets[3], sizeof(packets[3]));
   EXPECT_TRUE(voe_.CheckNoPacket(channel_num1));
   EXPECT_TRUE(voe_.CheckNoPacket(channel_num2));
   EXPECT_TRUE(voe_.CheckPacket(channel_num3, packets[3],
-                                      sizeof(packets[3])));
-  EXPECT_TRUE(channel_->RemoveStream(3));
-  EXPECT_TRUE(channel_->RemoveStream(2));
-  EXPECT_TRUE(channel_->RemoveStream(1));
+                               sizeof(packets[3])));
+  EXPECT_TRUE(channel_->RemoveRecvStream(3));
+  EXPECT_TRUE(channel_->RemoveRecvStream(2));
+  EXPECT_TRUE(channel_->RemoveRecvStream(1));
 }
 
 // Test that we properly handle failures to add a stream.
-TEST_F(WebRtcVoiceEngineTest, AddStreamFail) {
+TEST_F(WebRtcVoiceEngineTestFake, AddStreamFail) {
   EXPECT_TRUE(SetupEngine());
   voe_.set_fail_create_channel(true);
-  EXPECT_FALSE(channel_->AddStream(2));
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+  EXPECT_FALSE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+
+  // In 1:1 call, we should not try to create a new channel.
+  EXPECT_TRUE(channel_->SetOptions(0));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+}
+
+// Test that AddRecvStream doesn't create new channel for 1:1 call.
+TEST_F(WebRtcVoiceEngineTestFake, AddRecvStream1On1) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_EQ(channel_num, voe_.GetLastChannel());
 }
 
 // Test that we properly clean up any streams that were added, even if
 // not explicitly removed.
-TEST_F(WebRtcVoiceEngineTest, StreamCleanup) {
+TEST_F(WebRtcVoiceEngineTestFake, StreamCleanup) {
   EXPECT_TRUE(SetupEngine());
-  EXPECT_TRUE(channel_->AddStream(1));
-  EXPECT_TRUE(channel_->AddStream(2));
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
   EXPECT_EQ(3, voe_.GetNumChannels());  // default channel + 2 added
   delete channel_;
   channel_ = NULL;
@@ -928,7 +995,7 @@
 
 // Test that we can send DTMF properly, but only if the other side supports
 // telephone-event.
-TEST_F(WebRtcVoiceEngineTest, SendDtmf) {
+TEST_F(WebRtcVoiceEngineTestFake, SendDtmf) {
   EXPECT_TRUE(SetupEngine());
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kPcmuCodec);
@@ -941,7 +1008,7 @@
 }
 
 // Test that we can play a ringback tone properly in a single-stream call.
-TEST_F(WebRtcVoiceEngineTest, PlayRingback) {
+TEST_F(WebRtcVoiceEngineTestFake, PlayRingback) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = voe_.GetLastChannel();
   EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
@@ -963,10 +1030,11 @@
 }
 
 // Test that we can play a ringback tone properly in a multi-stream call.
-TEST_F(WebRtcVoiceEngineTest, PlayRingbackWithMultipleStreams) {
+TEST_F(WebRtcVoiceEngineTestFake, PlayRingbackWithMultipleStreams) {
   EXPECT_TRUE(SetupEngine());
-  EXPECT_TRUE(channel_->AddStream(1));
-  EXPECT_TRUE(channel_->AddStream(2));
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
   int channel_num = voe_.GetLastChannel();
   EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
   // Check we fail if no ringback tone specified.
@@ -995,7 +1063,7 @@
 }
 
 // Tests creating soundclips, and make sure they come from the right engine.
-TEST_F(WebRtcVoiceEngineTest, CreateSoundclip) {
+TEST_F(WebRtcVoiceEngineTestFake, CreateSoundclip) {
   EXPECT_TRUE(engine_.Init());
   soundclip_ = engine_.CreateSoundclip();
   ASSERT_TRUE(soundclip_ != NULL);
@@ -1009,7 +1077,7 @@
 }
 
 // Tests playing out a fake sound.
-TEST_F(WebRtcVoiceEngineTest, PlaySoundclip) {
+TEST_F(WebRtcVoiceEngineTestFake, PlaySoundclip) {
   static const char kZeroes[16000] = {};
   EXPECT_TRUE(engine_.Init());
   soundclip_ = engine_.CreateSoundclip();
@@ -1017,12 +1085,13 @@
   EXPECT_TRUE(soundclip_->PlaySound(kZeroes, sizeof(kZeroes), 0));
 }
 
-TEST_F(WebRtcVoiceEngineTest, MediaEngineCallbackOnError) {
+TEST_F(WebRtcVoiceEngineTestFake, MediaEngineCallbackOnError) {
   talk_base::scoped_ptr<ChannelErrorListener> listener;
   cricket::WebRtcVoiceMediaChannel* media_channel;
   unsigned int ssrc = 0;
 
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
 
   media_channel = reinterpret_cast<cricket::WebRtcVoiceMediaChannel*>(channel_);
@@ -1030,7 +1099,7 @@
 
   // Test on WebRtc VoE channel.
   voe_.TriggerCallbackOnError(media_channel->voe_channel(),
-                               VE_SATURATION_WARNING);
+                              VE_SATURATION_WARNING);
   EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_DEVICE_SATURATION,
             listener->error());
   EXPECT_NE(-1, voe_.GetLocalSSRC(voe_.GetLastChannel(), ssrc));
@@ -1044,10 +1113,11 @@
 
   // Add another stream and test on that.
   ++ssrc;
-  EXPECT_TRUE(channel_->AddStream(ssrc));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(
+      ssrc)));
   listener->Reset();
   voe_.TriggerCallbackOnError(voe_.GetLastChannel(),
-                               VE_SATURATION_WARNING);
+                              VE_SATURATION_WARNING);
   EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_DEVICE_SATURATION,
             listener->error());
   EXPECT_EQ(ssrc, listener->ssrc());
@@ -1055,28 +1125,103 @@
   // Testing a non-existing channel.
   listener->Reset();
   voe_.TriggerCallbackOnError(voe_.GetLastChannel() + 2,
-                               VE_SATURATION_WARNING);
+                              VE_SATURATION_WARNING);
   EXPECT_EQ(0, listener->error());
 }
 
-TEST_F(WebRtcVoiceEngineTest, TestSetPlayoutError) {
+TEST_F(WebRtcVoiceEngineTestFake, TestSetPlayoutError) {
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kPcmuCodec);
   EXPECT_TRUE(channel_->SetSendCodecs(codecs));
   EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
-  EXPECT_TRUE(channel_->AddStream(2));
-  EXPECT_TRUE(channel_->AddStream(3));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(3)));
   EXPECT_TRUE(channel_->SetPlayout(true));
   voe_.set_playout_fail_channel(voe_.GetLastChannel() - 1);
   EXPECT_TRUE(channel_->SetPlayout(false));
   EXPECT_FALSE(channel_->SetPlayout(true));
 }
 
+// Test that the Registering/Unregistering with the
+// webrtcvoiceengine works as expected
+TEST_F(WebRtcVoiceEngineTestFake, RegisterVoiceProcessor) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+  EXPECT_TRUE(channel_->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(kSsrc1)));
+  uint32 ssrc = 0;
+  voe_.GetLocalSSRC(0, ssrc);
+  cricket::FakeMediaProcessor vp_1;
+  cricket::FakeMediaProcessor vp_2;
+
+  EXPECT_TRUE(engine_.RegisterProcessor(ssrc, &vp_1, cricket::MPD_RX));
+  EXPECT_TRUE(engine_.RegisterProcessor(ssrc, &vp_2, cricket::MPD_RX));
+  voe_.TriggerProcessPacket(cricket::MPD_RX);
+  voe_.TriggerProcessPacket(cricket::MPD_TX);
+
+  EXPECT_TRUE(voe_.IsExternalMediaProcessorRegistered());
+  EXPECT_EQ(1, vp_1.voice_frame_count());
+  EXPECT_EQ(1, vp_2.voice_frame_count());
+
+  EXPECT_TRUE(engine_.UnregisterProcessor(ssrc,
+                                          &vp_2,
+                                          cricket::MPD_RX));
+  voe_.TriggerProcessPacket(cricket::MPD_RX);
+  EXPECT_TRUE(voe_.IsExternalMediaProcessorRegistered());
+  EXPECT_EQ(1, vp_2.voice_frame_count());
+  EXPECT_EQ(2, vp_1.voice_frame_count());
+
+  EXPECT_TRUE(engine_.UnregisterProcessor(ssrc,
+                                          &vp_1,
+                                          cricket::MPD_RX));
+  voe_.TriggerProcessPacket(cricket::MPD_RX);
+  EXPECT_FALSE(voe_.IsExternalMediaProcessorRegistered());
+  EXPECT_EQ(2, vp_1.voice_frame_count());
+
+  EXPECT_TRUE(engine_.RegisterProcessor(ssrc, &vp_1, cricket::MPD_TX));
+  voe_.TriggerProcessPacket(cricket::MPD_RX);
+  voe_.TriggerProcessPacket(cricket::MPD_TX);
+  EXPECT_TRUE(voe_.IsExternalMediaProcessorRegistered());
+  EXPECT_EQ(3, vp_1.voice_frame_count());
+
+  EXPECT_TRUE(engine_.UnregisterProcessor(ssrc,
+                                          &vp_1,
+                                          cricket::MPD_RX_AND_TX));
+  voe_.TriggerProcessPacket(cricket::MPD_TX);
+  EXPECT_FALSE(voe_.IsExternalMediaProcessorRegistered());
+  EXPECT_EQ(3, vp_1.voice_frame_count());
+  EXPECT_TRUE(channel_->RemoveRecvStream(kSsrc1));
+
+  // Test that after removing the recvstream we can we can still register
+  // the processor. This tests the 1:1 case.
+  EXPECT_TRUE(engine_.RegisterProcessor(ssrc, &vp_1, cricket::MPD_RX));
+  EXPECT_TRUE(engine_.UnregisterProcessor(ssrc, &vp_1, cricket::MPD_RX_AND_TX));
+
+  // The following tests test that FindChannelNumFromSsrc is doing
+  // what we expect.
+  // pick an invalid ssrc and make sure we can't register
+  EXPECT_FALSE(engine_.RegisterProcessor(0,
+                                         &vp_1,
+                                         cricket::MPD_RX));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(engine_.RegisterProcessor(1,
+                                        &vp_1,
+                                        cricket::MPD_RX));
+  EXPECT_TRUE(engine_.UnregisterProcessor(1,
+                                          &vp_1,
+                                          cricket::MPD_RX));
+  EXPECT_FALSE(engine_.RegisterProcessor(1,
+                                         &vp_1,
+                                         cricket::MPD_TX));
+  EXPECT_TRUE(channel_->RemoveRecvStream(1));
+}
+
 // Tests for the actual WebRtc VoE library.
 
 // Tests that the library initializes and shuts down properly.
-TEST(WebRtcVoiceEngineLibTest, StartupShutdown) {
+TEST(WebRtcVoiceEngineTest, StartupShutdown) {
   cricket::WebRtcVoiceEngine engine;
   EXPECT_TRUE(engine.Init());
   cricket::VoiceMediaChannel* channel = engine.CreateChannel();
@@ -1090,9 +1235,7 @@
 }
 
 // Tests that the logging from the library is cleartext.
-// TODO: This test case is disabled due to a known bug in VoE4.0
-// which sends out truncated log message. Will be fixed in next VoE release.
-TEST(WebRtcVoiceEngineLibTest, DISABLED_HasUnencryptedLogging) {
+TEST(WebRtcVoiceEngineTest, DISABLED_HasUnencryptedLogging) {
   cricket::WebRtcVoiceEngine engine;
   talk_base::scoped_ptr<talk_base::MemoryStream> stream(
       new talk_base::MemoryStream);
@@ -1117,7 +1260,7 @@
 
 // Tests we do not see any references to a monitor thread being spun up
 // when initiating the engine.
-TEST(WebRtcVoiceEngineLibTest, HasNoMonitorThread) {
+TEST(WebRtcVoiceEngineTest, HasNoMonitorThread) {
   cricket::WebRtcVoiceEngine engine;
   talk_base::scoped_ptr<talk_base::MemoryStream> stream(
       new talk_base::MemoryStream);
@@ -1135,7 +1278,7 @@
 }
 
 // Tests that the library is configured with the codecs we want.
-TEST(WebRtcVoiceEngineLibTest, HasCorrectCodecs) {
+TEST(WebRtcVoiceEngineTest, HasCorrectCodecs) {
   cricket::WebRtcVoiceEngine engine;
   // Check codecs by name.
   EXPECT_TRUE(engine.FindCodec(
@@ -1211,7 +1354,7 @@
 }
 
 // Tests that VoE supports at least 32 channels
-TEST(WebRtcVoiceEngineLibTest, Has32Channels) {
+TEST(WebRtcVoiceEngineTest, Has32Channels) {
   cricket::WebRtcVoiceEngine engine;
   EXPECT_TRUE(engine.Init());
 
@@ -1238,7 +1381,7 @@
 
 #ifdef WIN32
 // Test our workarounds to WebRtc VoE' munging of the coinit count
-TEST(WebRtcVoiceEngineLibTest, CoInitialize) {
+TEST(WebRtcVoiceEngineTest, CoInitialize) {
   cricket::WebRtcVoiceEngine* engine = new cricket::WebRtcVoiceEngine();
 
   // Initial refcount should be 0.
diff --git a/talk/session/phone/win32devicemanager.cc b/talk/session/phone/win32devicemanager.cc
new file mode 100644
index 0000000..f8a928e
--- /dev/null
+++ b/talk/session/phone/win32devicemanager.cc
@@ -0,0 +1,405 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/win32devicemanager.h"
+
+#include <atlbase.h>
+#include <dbt.h>
+#include <strmif.h>  // must come before ks.h
+#include <ks.h>
+#include <ksmedia.h>
+#define INITGUID  // For PKEY_AudioEndpoint_GUID
+#include <mmdeviceapi.h>
+#include <mmsystem.h>
+#include <functiondiscoverykeys_devpkey.h>
+#include <uuids.h>
+
+#include "talk/base/win32.h"  // ToUtf8
+#include "talk/base/win32window.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/mediacommon.h"
+#ifdef HAVE_LOGITECH_HEADERS
+#include "third_party/logitech/files/logitechquickcam.h"
+#endif
+
+namespace cricket {
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+  return new Win32DeviceManager();
+}
+
+class Win32DeviceWatcher
+    : public DeviceWatcher,
+      public talk_base::Win32Window {
+ public:
+  explicit Win32DeviceWatcher(Win32DeviceManager* dm);
+  virtual ~Win32DeviceWatcher();
+  virtual bool Start();
+  virtual void Stop();
+
+ private:
+  HDEVNOTIFY Register(REFGUID guid);
+  void Unregister(HDEVNOTIFY notify);
+  virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result);
+
+  Win32DeviceManager* manager_;
+  HDEVNOTIFY audio_notify_;
+  HDEVNOTIFY video_notify_;
+};
+
+static const char* kFilteredAudioDevicesName[] = {
+    NULL,
+};
+static const char* const kFilteredVideoDevicesName[] =  {
+    "Google Camera Adapter",   // Our own magiccams
+    "Asus virtual Camera",     // Bad Asus desktop virtual cam
+    "Bluetooth Video",         // Bad Sony viao bluetooth sharing driver
+    NULL,
+};
+static const wchar_t kFriendlyName[] = L"FriendlyName";
+static const wchar_t kDevicePath[] = L"DevicePath";
+static const char kUsbDevicePathPrefix[] = "\\\\?\\usb";
+static bool GetDevices(const CLSID& catid, std::vector<Device>* out);
+static bool GetCoreAudioDevices(bool input, std::vector<Device>* devs);
+static bool GetWaveDevices(bool input, std::vector<Device>* devs);
+
+Win32DeviceManager::Win32DeviceManager()
+    : need_couninitialize_(false) {
+  set_watcher(new Win32DeviceWatcher(this));
+}
+
+Win32DeviceManager::~Win32DeviceManager() {
+  if (initialized()) {
+    Terminate();
+  }
+}
+
+bool Win32DeviceManager::Init() {
+  if (!initialized()) {
+    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+    need_couninitialize_ = SUCCEEDED(hr);
+    if (FAILED(hr)) {
+      LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr;
+      if (hr != RPC_E_CHANGED_MODE) {
+        return false;
+      }
+    }
+    if (!watcher()->Start()) {
+      return false;
+    }
+    set_initialized(true);
+  }
+  return true;
+}
+
+void Win32DeviceManager::Terminate() {
+  if (initialized()) {
+    watcher()->Stop();
+    if (need_couninitialize_) {
+      CoUninitialize();
+      need_couninitialize_ = false;
+    }
+    set_initialized(false);
+  }
+}
+
+bool Win32DeviceManager::GetDefaultVideoCaptureDevice(Device* device) {
+  bool ret = false;
+  // If there are multiple capture devices, we want the first USB one.
+  // This avoids issues with defaulting to virtual cameras or grabber cards.
+  std::vector<Device> devices;
+  ret = (GetVideoCaptureDevices(&devices) && !devices.empty());
+  if (ret) {
+    *device = devices[0];
+    for (size_t i = 0; i < devices.size(); ++i) {
+      if (strnicmp(devices[i].id.c_str(), kUsbDevicePathPrefix,
+                   ARRAY_SIZE(kUsbDevicePathPrefix) - 1) == 0) {
+        *device = devices[i];
+        break;
+      }
+    }
+  }
+  return ret;
+}
+
+bool Win32DeviceManager::GetAudioDevices(bool input,
+                                         std::vector<Device>* devs) {
+  devs->clear();
+
+  if (talk_base::IsWindowsVistaOrLater()) {
+    if (!GetCoreAudioDevices(input, devs))
+      return false;
+  } else {
+    if (!GetWaveDevices(input, devs))
+      return false;
+  }
+  return FilterDevices(devs, kFilteredAudioDevicesName);
+}
+
+bool Win32DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
+  devices->clear();
+  if (!GetDevices(CLSID_VideoInputDeviceCategory, devices)) {
+    return false;
+  }
+  return FilterDevices(devices, kFilteredVideoDevicesName);
+}
+
+bool GetDevices(const CLSID& catid, std::vector<Device>* devices) {
+  HRESULT hr;
+
+  // CComPtr is a scoped pointer that will be auto released when going
+  // out of scope. CoUninitialize must not be called before the
+  // release.
+  CComPtr<ICreateDevEnum> sys_dev_enum;
+  CComPtr<IEnumMoniker> cam_enum;
+  if (FAILED(hr = sys_dev_enum.CoCreateInstance(CLSID_SystemDeviceEnum)) ||
+      FAILED(hr = sys_dev_enum->CreateClassEnumerator(catid, &cam_enum, 0))) {
+    LOG(LS_ERROR) << "Failed to create device enumerator, hr="  << hr;
+    return false;
+  }
+
+  // Only enum devices if CreateClassEnumerator returns S_OK. If there are no
+  // devices available, S_FALSE will be returned, but enumMk will be NULL.
+  if (hr == S_OK) {
+    CComPtr<IMoniker> mk;
+    while (cam_enum->Next(1, &mk, NULL) == S_OK) {
+#ifdef HAVE_LOGITECH_HEADERS
+      // Initialize Logitech device if applicable
+      MaybeLogitechDeviceReset(mk);
+#endif
+      CComPtr<IPropertyBag> bag;
+      if (SUCCEEDED(mk->BindToStorage(NULL, NULL,
+          __uuidof(bag), reinterpret_cast<void**>(&bag)))) {
+        CComVariant name, path;
+        std::string name_str, path_str;
+        if (SUCCEEDED(bag->Read(kFriendlyName, &name, 0)) &&
+            name.vt == VT_BSTR) {
+          name_str = talk_base::ToUtf8(name.bstrVal);
+          // Get the device id if one exists.
+          if (SUCCEEDED(bag->Read(kDevicePath, &path, 0)) &&
+              path.vt == VT_BSTR) {
+            path_str = talk_base::ToUtf8(path.bstrVal);
+          }
+
+          devices->push_back(Device(name_str, path_str));
+        }
+      }
+      mk = NULL;
+    }
+  }
+
+  return true;
+}
+
+HRESULT GetStringProp(IPropertyStore* bag, PROPERTYKEY key, std::string* out) {
+  out->clear();
+  PROPVARIANT var;
+  PropVariantInit(&var);
+
+  HRESULT hr = bag->GetValue(key, &var);
+  if (SUCCEEDED(hr)) {
+    if (var.pwszVal)
+      *out = talk_base::ToUtf8(var.pwszVal);
+    else
+      hr = E_FAIL;
+  }
+
+  PropVariantClear(&var);
+  return hr;
+}
+
+// Adapted from http://msdn.microsoft.com/en-us/library/dd370812(v=VS.85).aspx
+HRESULT CricketDeviceFromImmDevice(IMMDevice* device, Device* out) {
+  CComPtr<IPropertyStore> props;
+
+  HRESULT hr = device->OpenPropertyStore(STGM_READ, &props);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // Get the endpoint's name and id.
+  std::string name, guid;
+  hr = GetStringProp(props, PKEY_Device_FriendlyName, &name);
+  if (SUCCEEDED(hr)) {
+    hr = GetStringProp(props, PKEY_AudioEndpoint_GUID, &guid);
+
+    if (SUCCEEDED(hr)) {
+      out->name = name;
+      out->id = guid;
+    }
+  }
+  return hr;
+}
+
+bool GetCoreAudioDevices(
+    bool input, std::vector<Device>* devs) {
+  HRESULT hr = S_OK;
+  CComPtr<IMMDeviceEnumerator> enumerator;
+
+  hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
+      __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&enumerator));
+  if (SUCCEEDED(hr)) {
+    CComPtr<IMMDeviceCollection> devices;
+    hr = enumerator->EnumAudioEndpoints((input ? eCapture : eRender),
+                                        DEVICE_STATE_ACTIVE, &devices);
+    if (SUCCEEDED(hr)) {
+      unsigned int count;
+      hr = devices->GetCount(&count);
+
+      if (SUCCEEDED(hr)) {
+        for (unsigned int i = 0; i < count; i++) {
+          CComPtr<IMMDevice> device;
+
+          // Get pointer to endpoint number i.
+          hr = devices->Item(i, &device);
+          if (FAILED(hr)) {
+            break;
+          }
+
+          Device dev;
+          hr = CricketDeviceFromImmDevice(device, &dev);
+          if (SUCCEEDED(hr)) {
+            devs->push_back(dev);
+          } else {
+            LOG(LS_WARNING) << "Unable to query IMM Device, skipping.  HR="
+                            << hr;
+            hr = S_FALSE;
+          }
+        }
+      }
+    }
+  }
+
+  if (FAILED(hr)) {
+    LOG(LS_WARNING) << "GetCoreAudioDevices failed with hr " << hr;
+    return false;
+  }
+  return true;
+}
+
+bool GetWaveDevices(bool input, std::vector<Device>* devs) {
+  // Note, we don't use the System Device Enumerator interface here since it
+  // adds lots of pseudo-devices to the list, such as DirectSound and Wave
+  // variants of the same device.
+  if (input) {
+    int num_devs = waveInGetNumDevs();
+    for (int i = 0; i < num_devs; ++i) {
+      WAVEINCAPS caps;
+      if (waveInGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
+          caps.wChannels > 0) {
+        devs->push_back(Device(talk_base::ToUtf8(caps.szPname),
+                               talk_base::ToString(i)));
+      }
+    }
+  } else {
+    int num_devs = waveOutGetNumDevs();
+    for (int i = 0; i < num_devs; ++i) {
+      WAVEOUTCAPS caps;
+      if (waveOutGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
+          caps.wChannels > 0) {
+        devs->push_back(Device(talk_base::ToUtf8(caps.szPname), i));
+      }
+    }
+  }
+  return true;
+}
+
+Win32DeviceWatcher::Win32DeviceWatcher(Win32DeviceManager* manager)
+    : DeviceWatcher(manager),
+      manager_(manager),
+      audio_notify_(NULL),
+      video_notify_(NULL) {
+}
+
+Win32DeviceWatcher::~Win32DeviceWatcher() {
+}
+
+bool Win32DeviceWatcher::Start() {
+  if (!Create(NULL, _T("libjingle Win32DeviceWatcher Window"),
+              0, 0, 0, 0, 0, 0)) {
+    return false;
+  }
+
+  audio_notify_ = Register(KSCATEGORY_AUDIO);
+  if (!audio_notify_) {
+    Stop();
+    return false;
+  }
+
+  video_notify_ = Register(KSCATEGORY_VIDEO);
+  if (!video_notify_) {
+    Stop();
+    return false;
+  }
+
+  return true;
+}
+
+void Win32DeviceWatcher::Stop() {
+  UnregisterDeviceNotification(video_notify_);
+  video_notify_ = NULL;
+  UnregisterDeviceNotification(audio_notify_);
+  audio_notify_ = NULL;
+  Destroy();
+}
+
+HDEVNOTIFY Win32DeviceWatcher::Register(REFGUID guid) {
+  DEV_BROADCAST_DEVICEINTERFACE dbdi;
+  dbdi.dbcc_size = sizeof(dbdi);
+  dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+  dbdi.dbcc_classguid = guid;
+  dbdi.dbcc_name[0] = '\0';
+  return RegisterDeviceNotification(handle(), &dbdi,
+                                    DEVICE_NOTIFY_WINDOW_HANDLE);
+}
+
+void Win32DeviceWatcher::Unregister(HDEVNOTIFY handle) {
+  UnregisterDeviceNotification(handle);
+}
+
+bool Win32DeviceWatcher::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+                              LRESULT& result) {
+  if (uMsg == WM_DEVICECHANGE) {
+    if (wParam == DBT_DEVICEARRIVAL ||
+        wParam == DBT_DEVICEREMOVECOMPLETE) {
+      DEV_BROADCAST_DEVICEINTERFACE* dbdi =
+          reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(lParam);
+      if (dbdi->dbcc_classguid == KSCATEGORY_AUDIO ||
+        dbdi->dbcc_classguid == KSCATEGORY_VIDEO) {
+        manager_->SignalDevicesChange();
+      }
+    }
+    result = 0;
+    return true;
+  }
+
+  return false;
+}
+
+};  // namespace cricket
diff --git a/talk/session/phone/win32devicemanager.h b/talk/session/phone/win32devicemanager.h
new file mode 100644
index 0000000..98e73c1
--- /dev/null
+++ b/talk/session/phone/win32devicemanager.h
@@ -0,0 +1,60 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_WIN32DEVICEMANAGER_H_
+#define TALK_SESSION_PHONE_WIN32DEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/phone/devicemanager.h"
+
+namespace cricket {
+
+class Win32DeviceManager : public DeviceManager {
+ public:
+  Win32DeviceManager();
+  virtual ~Win32DeviceManager();
+
+  // Initialization
+  virtual bool Init();
+  virtual void Terminate();
+
+  virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
+
+ private:
+  virtual bool GetAudioDevices(bool input, std::vector<Device>* devs);
+  virtual bool GetDefaultVideoCaptureDevice(Device* device);
+
+  bool need_couninitialize_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_WIN32DEVICEMANAGER_H_
diff --git a/talk/session/tunnel/securetunnelsessionclient.cc b/talk/session/tunnel/securetunnelsessionclient.cc
index 84ec5e0..71f38aa 100644
--- a/talk/session/tunnel/securetunnelsessionclient.cc
+++ b/talk/session/tunnel/securetunnelsessionclient.cc
@@ -44,15 +44,16 @@
 
 // XML elements and namespaces for XMPP stanzas used in content exchanges.
 
-const std::string NS_SECURE_TUNNEL("http://www.google.com/talk/securetunnel");
-const buzz::QName QN_SECURE_TUNNEL_DESCRIPTION(NS_SECURE_TUNNEL,
-                                               "description");
-const buzz::QName QN_SECURE_TUNNEL_TYPE(NS_SECURE_TUNNEL, "type");
-const buzz::QName QN_SECURE_TUNNEL_CLIENT_CERT(NS_SECURE_TUNNEL,
-                                               "client-cert");
-const buzz::QName QN_SECURE_TUNNEL_SERVER_CERT(NS_SECURE_TUNNEL,
-                                               "server-cert");
-const std::string CN_SECURE_TUNNEL("securetunnel");
+const char NS_SECURE_TUNNEL[] = "http://www.google.com/talk/securetunnel";
+const buzz::StaticQName QN_SECURE_TUNNEL_DESCRIPTION =
+    { NS_SECURE_TUNNEL, "description" };
+const buzz::StaticQName QN_SECURE_TUNNEL_TYPE =
+    { NS_SECURE_TUNNEL, "type" };
+const buzz::StaticQName QN_SECURE_TUNNEL_CLIENT_CERT =
+    { NS_SECURE_TUNNEL, "client-cert" };
+const buzz::StaticQName QN_SECURE_TUNNEL_SERVER_CERT =
+    { NS_SECURE_TUNNEL, "server-cert" };
+const char CN_SECURE_TUNNEL[] = "securetunnel";
 
 // SecureTunnelContentDescription
 
diff --git a/talk/session/tunnel/tunnelsessionclient.cc b/talk/session/tunnel/tunnelsessionclient.cc
index 13d5c10..05cc757 100644
--- a/talk/session/tunnel/tunnelsessionclient.cc
+++ b/talk/session/tunnel/tunnelsessionclient.cc
@@ -39,10 +39,10 @@
 
 namespace cricket {
 
-const std::string NS_TUNNEL("http://www.google.com/talk/tunnel");
-const buzz::QName QN_TUNNEL_DESCRIPTION(NS_TUNNEL, "description");
-const buzz::QName QN_TUNNEL_TYPE(NS_TUNNEL, "type");
-const std::string CN_TUNNEL("tunnel");
+const char NS_TUNNEL[] = "http://www.google.com/talk/tunnel";
+const buzz::StaticQName QN_TUNNEL_DESCRIPTION = { NS_TUNNEL, "description" };
+const buzz::StaticQName QN_TUNNEL_TYPE = { NS_TUNNEL, "type" };
+const char CN_TUNNEL[] = "tunnel";
 
 enum {
   MSG_CLOCK = 1,
diff --git a/talk/session/tunnel/tunnelsessionclient_unittest.cc b/talk/session/tunnel/tunnelsessionclient_unittest.cc
new file mode 100644
index 0000000..7ead1bf
--- /dev/null
+++ b/talk/session/tunnel/tunnelsessionclient_unittest.cc
@@ -0,0 +1,235 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "talk/base/gunit.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/client/fakeportallocator.h"
+#include "talk/session/tunnel/tunnelsessionclient.h"
+
+static const int kTimeoutMs = 10000;
+static const int kBlockSize = 4096;
+static const buzz::Jid kLocalJid("local@localhost");
+static const buzz::Jid kRemoteJid("remote@localhost");
+
+// This test fixture creates the necessary plumbing to create and run
+// two TunnelSessionClients that talk to each other.
+class TunnelSessionClientTest : public testing::Test,
+                                public talk_base::MessageHandler,
+                                public sigslot::has_slots<> {
+ public:
+  TunnelSessionClientTest()
+      : local_pa_(talk_base::Thread::Current(), NULL),
+        remote_pa_(talk_base::Thread::Current(), NULL),
+        local_sm_(&local_pa_, talk_base::Thread::Current()),
+        remote_sm_(&remote_pa_, talk_base::Thread::Current()),
+        local_client_(kLocalJid, &local_sm_),
+        remote_client_(kRemoteJid, &remote_sm_),
+        done_(false) {
+    local_sm_.SignalSessionCreate.connect(this,
+        &TunnelSessionClientTest::OnSessionCreate);
+    local_sm_.SignalRequestSignaling.connect(this,
+        &TunnelSessionClientTest::OnLocalRequestSignaling);
+    local_sm_.SignalOutgoingMessage.connect(this,
+        &TunnelSessionClientTest::OnOutgoingMessage);
+    remote_sm_.SignalSessionCreate.connect(this,
+        &TunnelSessionClientTest::OnSessionCreate);
+    remote_sm_.SignalRequestSignaling.connect(this,
+        &TunnelSessionClientTest::OnRemoteRequestSignaling);
+    remote_sm_.SignalOutgoingMessage.connect(this,
+        &TunnelSessionClientTest::OnOutgoingMessage);
+    remote_client_.SignalIncomingTunnel.connect(this,
+        &TunnelSessionClientTest::OnIncomingTunnel);
+  }
+
+  // Transfer the desired amount of data from the local to the remote client.
+  void TestTransfer(int size) {
+    // Create some dummy data to send.
+    send_stream_.ReserveSize(size);
+    for (int i = 0; i < size; ++i) {
+      char ch = static_cast<char>(i);
+      send_stream_.Write(&ch, 1, NULL, NULL);
+    }
+    send_stream_.Rewind();
+    // Prepare the receive stream.
+    recv_stream_.ReserveSize(size);
+    // Create the tunnel and set things in motion.
+    local_tunnel_.reset(local_client_.CreateTunnel(kRemoteJid, "test"));
+    local_tunnel_->SignalEvent.connect(this,
+        &TunnelSessionClientTest::OnStreamEvent);
+    EXPECT_TRUE_WAIT(done_, kTimeoutMs);
+    // Make sure we received the right data.
+    EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(),
+                        recv_stream_.GetBuffer(), size));
+  }
+
+ private:
+  enum { MSG_LSIGNAL, MSG_RSIGNAL };
+
+  // Use this callback to allow local ips for this test.
+  void OnSessionCreate(cricket::Session* session, bool incoming) {
+    session->set_allow_local_ips(true);
+  }
+
+  // There's no SessionManager* argument in this callback, so we need 2 of them.
+  void OnLocalRequestSignaling() {
+    local_sm_.OnSignalingReady();
+  }
+  void OnRemoteRequestSignaling() {
+    remote_sm_.OnSignalingReady();
+  }
+
+  // Post a message, to avoid problems with directly connecting the callbacks.
+  void OnOutgoingMessage(cricket::SessionManager* manager,
+                         const buzz::XmlElement* stanza) {
+    if (manager == &local_sm_) {
+      talk_base::Thread::Current()->Post(this, MSG_LSIGNAL,
+          talk_base::WrapMessageData(*stanza));
+    } else if (manager == &remote_sm_) {
+      talk_base::Thread::Current()->Post(this, MSG_RSIGNAL,
+          talk_base::WrapMessageData(*stanza));
+    }
+  }
+
+  // Need to add a "from=" attribute (normally added by the server)
+  // Then route the incoming signaling message to the "other" session manager.
+  virtual void OnMessage(talk_base::Message* message) {
+    talk_base::TypedMessageData<buzz::XmlElement>* data =
+        static_cast<talk_base::TypedMessageData<buzz::XmlElement>*>(
+            message->pdata);
+    bool response = data->data().Attr(buzz::QN_TYPE) == buzz::STR_RESULT;
+    if (message->message_id == MSG_RSIGNAL) {
+      data->data().AddAttr(buzz::QN_FROM, remote_client_.jid().Str());
+      if (!response) {
+        local_sm_.OnIncomingMessage(&data->data());
+      } else {
+        local_sm_.OnIncomingResponse(NULL, &data->data());
+      }
+    } else if (message->message_id == MSG_LSIGNAL) {
+      data->data().AddAttr(buzz::QN_FROM, local_client_.jid().Str());
+      if (!response) {
+        remote_sm_.OnIncomingMessage(&data->data());
+      } else {
+        remote_sm_.OnIncomingResponse(NULL, &data->data());
+      }
+    }
+    delete data;
+  }
+
+  // Accept the tunnel when it arrives and wire up the stream.
+  void OnIncomingTunnel(cricket::TunnelSessionClient* client,
+                        buzz::Jid jid, std::string description,
+                        cricket::Session* session) {
+    remote_tunnel_.reset(remote_client_.AcceptTunnel(session));
+    remote_tunnel_->SignalEvent.connect(this,
+        &TunnelSessionClientTest::OnStreamEvent);
+  }
+
+  // Send from send_stream_ as long as we're not flow-controlled.
+  // Read bytes out into recv_stream_ as they arrive.
+  // End the test when we are notified that the local side has closed the
+  // tunnel. All data has been read out at this point.
+  void OnStreamEvent(talk_base::StreamInterface* stream, int events,
+                     int error) {
+    if (events & talk_base::SE_READ) {
+      if (stream == remote_tunnel_.get()) {
+        ReadData();
+      }
+    }
+    if (events & talk_base::SE_WRITE) {
+      if (stream == local_tunnel_.get()) {
+        bool done = false;
+        WriteData(&done);
+        if (done) {
+          local_tunnel_->Close();
+        }
+      }
+    }
+    if (events & talk_base::SE_CLOSE) {
+      if (stream == remote_tunnel_.get()) {
+        remote_tunnel_->Close();
+        done_ = true;
+      }
+    }
+  }
+
+  // Spool from the tunnel into recv_stream.
+  // Flow() doesn't work here because it won't write if the read blocks.
+  void ReadData() {
+    char block[kBlockSize];
+    size_t read, position;
+    talk_base::StreamResult res;
+    while ((res = remote_tunnel_->Read(block, sizeof(block), &read, NULL)) ==
+        talk_base::SR_SUCCESS) {
+      recv_stream_.Write(block, read, NULL, NULL);
+    }
+    ASSERT(res != talk_base::SR_EOS);
+    recv_stream_.GetPosition(&position);
+    LOG(LS_VERBOSE) << "Recv position: " << position;
+  }
+  // Spool from send_stream into the tunnel. Back up if we get flow controlled.
+  void WriteData(bool* done) {
+    char block[kBlockSize];
+    size_t leftover = 0, position;
+    talk_base::StreamResult res = talk_base::Flow(&send_stream_,
+        block, sizeof(block), local_tunnel_.get(), &leftover);
+    if (res == talk_base::SR_BLOCK) {
+      send_stream_.GetPosition(&position);
+      send_stream_.SetPosition(position - leftover);
+      LOG(LS_VERBOSE) << "Send position: " << position - leftover;
+      *done = false;
+    } else if (res == talk_base::SR_SUCCESS) {
+      *done = true;
+    } else {
+      ASSERT(false);  // shouldn't happen
+    }
+  }
+
+ private:
+  cricket::FakePortAllocator local_pa_;
+  cricket::FakePortAllocator remote_pa_;
+  cricket::SessionManager local_sm_;
+  cricket::SessionManager remote_sm_;
+  cricket::TunnelSessionClient local_client_;
+  cricket::TunnelSessionClient remote_client_;
+  talk_base::scoped_ptr<talk_base::StreamInterface> local_tunnel_;
+  talk_base::scoped_ptr<talk_base::StreamInterface> remote_tunnel_;
+  talk_base::MemoryStream send_stream_;
+  talk_base::MemoryStream recv_stream_;
+  bool done_;
+};
+
+// Test the normal case of sending data from one side to the other.
+TEST_F(TunnelSessionClientTest, TestTransfer) {
+  TestTransfer(1000000);
+}
diff --git a/talk/site_scons/talk.py b/talk/site_scons/talk.py
index 87650fa..2989160 100644
--- a/talk/site_scons/talk.py
+++ b/talk/site_scons/talk.py
@@ -417,15 +417,6 @@
   return merged
 
 
-# Whether to enable extra 64-bit targets marked with the also64bit setting. Used
-# only for the legacy mixed 32/64 desktop Linux build.
-def EnableExtra64BitTargets(env):
-  return (env.Bit('linux') and env.Bit('build_platform_64bit') and
-          not env.Bit('host_platform_64bit') and
-          not env.Bit('host_platform_mipsel')
-          )
-
-
 def MergeSettingsFromLibraryDependencies(env, params):
   if 'libs' in params:
     for lib in params['libs']:
@@ -523,13 +514,6 @@
     if values is not None:
       env.Prepend(**{var : values})
 
-  # workaround for pulse stripping link flag for unknown reason
-  if EnableExtra64BitTargets(env):
-    env['SHLINKCOM'] = ('$SHLINK -o $TARGET -m32 $SHLINKFLAGS $SOURCES '
-                        '$_LIBDIRFLAGS $_LIBFLAGS')
-    env['LINKCOM'] = ('$LINK -o $TARGET -m32 $LINKFLAGS $SOURCES '
-                      '$_LIBDIRFLAGS $_LIBFLAGS')
-
   # any other parameters are replaced without renaming
   for field, value in params.items():
     env.Replace(**{field : value})
@@ -556,36 +540,7 @@
 
   node = builder(name, srcs)
 
-  # make a parallel 64bit version if requested
-  if EnableExtra64BitTargets(env) and PopEntry(params, 'also64bit'):
-    env_64bit = env.Clone()
-    env_64bit.FilterOut(CCFLAGS = ['-m32'], LINKFLAGS = ['-m32'])
-    env_64bit.Prepend(CCFLAGS = ['-m64', '-fPIC'], LINKFLAGS = ['-m64'])
-    name_64bit = name + '64'
-    env_64bit.Replace(OBJSUFFIX = '64' + env_64bit['OBJSUFFIX'])
-    env_64bit.Replace(SHOBJSUFFIX = '64' + env_64bit['SHOBJSUFFIX'])
-    if ('ComponentProgram' == component or
-        ('ComponentLibrary' == component and
-         env_64bit['COMPONENT_STATIC'] == False)):
-      # link 64 bit versions of libraries
-      libs = []
-      for lib in env_64bit['LIBS']:
-        libparams = _GetLibParams(env, lib)
-        if libparams and 'also64bit' in libparams:
-          libs.append(lib + '64')
-        else:
-          libs.append(lib)
-      env_64bit.Replace(LIBS = libs)
-
-    env_64bit['SHLINKCOM'] = ('$SHLINK -o $TARGET -m64 $SHLINKFLAGS $SOURCES '
-                              '$_LIBDIRFLAGS $_LIBFLAGS')
-    env_64bit['LINKCOM'] = ('$LINK -o $TARGET -m64 $LINKFLAGS $SOURCES '
-                            '$_LIBDIRFLAGS $_LIBFLAGS')
-    builder = getattr(env_64bit, component)
-    nodes = [node, builder(name_64bit, srcs)]
-    return nodes
-
-  if signed:  # Note currently incompatible with 64Bit flag
+  if signed:
     # Get the name of the built binary, then get the name of the final signed
     # version from it.  We need the output path since we don't know the file
     # extension beforehand.
diff --git a/talk/sound/alsasoundsystem.cc b/talk/sound/alsasoundsystem.cc
index fdfb800..c6dc73a 100644
--- a/talk/sound/alsasoundsystem.cc
+++ b/talk/sound/alsasoundsystem.cc
@@ -31,6 +31,7 @@
 #include "talk/base/logging.h"
 #include "talk/base/scoped_ptr.h"
 #include "talk/base/stringutils.h"
+#include "talk/base/timeutils.h"
 #include "talk/base/worker.h"
 #include "talk/sound/sounddevicelocator.h"
 #include "talk/sound/soundinputstreaminterface.h"
@@ -59,9 +60,6 @@
 // The latency we'll use for kNoLatencyRequirements (chosen arbitrarily).
 static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2;
 
-static const int kUsecsPerSec = 1000000;
-static const int kUsecsPerMsec = 1000;
-
 // We translate newlines in ALSA device descriptions to hyphens.
 static const char kAlsaDescriptionSearch[] = "\n";
 static const char kAlsaDescriptionReplace[] = " - ";
@@ -172,7 +170,7 @@
       return 0;
     }
     // The delay is in frames. Convert to microseconds.
-    return delay * kUsecsPerSec / freq_;
+    return delay * talk_base::kNumMicrosecsPerSec / freq_;
   }
 
   // Used to recover from certain recoverable errors, principally buffer overrun
@@ -656,7 +654,7 @@
         int freq)) {
 
   if (!IsInitialized()) {
-    return false;
+    return NULL;
   }
 
   StreamInterface *stream;
@@ -686,7 +684,7 @@
   } else {
     // kLowLatency is 0, so we treat it the same as a request for zero latency.
     // Compute what the user asked for.
-    latency = kUsecsPerSec *
+    latency = talk_base::kNumMicrosecsPerSec *
         params.latency /
         params.freq /
         FrameSize(params);
@@ -721,7 +719,7 @@
       FrameSize(params),
       // We set the wait time to twice the requested latency, so that wait
       // timeouts should be rare.
-      2 * latency / kUsecsPerMsec,
+      2 * latency / talk_base::kNumMicrosecsPerMillisec,
       params.flags,
       params.freq);
   if (stream) {
diff --git a/talk/sound/automaticallychosensoundsystem_unittest.cc b/talk/sound/automaticallychosensoundsystem_unittest.cc
new file mode 100644
index 0000000..a8afeec
--- /dev/null
+++ b/talk/sound/automaticallychosensoundsystem_unittest.cc
@@ -0,0 +1,214 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/sound/automaticallychosensoundsystem.h"
+#include "talk/sound/nullsoundsystem.h"
+
+namespace cricket {
+
+class NeverFailsToFailSoundSystem : public NullSoundSystem {
+ public:
+  // Overrides superclass.
+  virtual bool Init() {
+    return false;
+  }
+
+  static SoundSystemInterface *Create() {
+    return new NeverFailsToFailSoundSystem();
+  }
+};
+
+class InitCheckingSoundSystem1 : public NullSoundSystem {
+ public:
+  // Overrides superclass.
+  virtual bool Init() {
+    created_ = true;
+    return true;
+  }
+
+  static SoundSystemInterface *Create() {
+    return new InitCheckingSoundSystem1();
+  }
+
+  static bool created_;
+};
+
+bool InitCheckingSoundSystem1::created_ = false;
+
+class InitCheckingSoundSystem2 : public NullSoundSystem {
+ public:
+  // Overrides superclass.
+  virtual bool Init() {
+    created_ = true;
+    return true;
+  }
+
+  static SoundSystemInterface *Create() {
+    return new InitCheckingSoundSystem2();
+  }
+
+  static bool created_;
+};
+
+bool InitCheckingSoundSystem2::created_ = false;
+
+class DeletionCheckingSoundSystem1 : public NeverFailsToFailSoundSystem {
+ public:
+  virtual ~DeletionCheckingSoundSystem1() {
+    deleted_ = true;
+  }
+
+  static SoundSystemInterface *Create() {
+    return new DeletionCheckingSoundSystem1();
+  }
+
+  static bool deleted_;
+};
+
+bool DeletionCheckingSoundSystem1::deleted_ = false;
+
+class DeletionCheckingSoundSystem2 : public NeverFailsToFailSoundSystem {
+ public:
+  virtual ~DeletionCheckingSoundSystem2() {
+    deleted_ = true;
+  }
+
+  static SoundSystemInterface *Create() {
+    return new DeletionCheckingSoundSystem2();
+  }
+
+  static bool deleted_;
+};
+
+bool DeletionCheckingSoundSystem2::deleted_ = false;
+
+class DeletionCheckingSoundSystem3 : public NullSoundSystem {
+ public:
+  virtual ~DeletionCheckingSoundSystem3() {
+    deleted_ = true;
+  }
+
+  static SoundSystemInterface *Create() {
+    return new DeletionCheckingSoundSystem3();
+  }
+
+  static bool deleted_;
+};
+
+bool DeletionCheckingSoundSystem3::deleted_ = false;
+
+extern const SoundSystemCreator kSingleSystemFailingCreators[] = {
+  &NeverFailsToFailSoundSystem::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, SingleSystemFailing) {
+  AutomaticallyChosenSoundSystem<
+      kSingleSystemFailingCreators,
+      ARRAY_SIZE(kSingleSystemFailingCreators)> sound_system;
+  EXPECT_FALSE(sound_system.Init());
+}
+
+extern const SoundSystemCreator kSingleSystemSucceedingCreators[] = {
+  &NullSoundSystem::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, SingleSystemSucceeding) {
+  AutomaticallyChosenSoundSystem<
+      kSingleSystemSucceedingCreators,
+      ARRAY_SIZE(kSingleSystemSucceedingCreators)> sound_system;
+  EXPECT_TRUE(sound_system.Init());
+}
+
+extern const SoundSystemCreator
+    kFailedFirstSystemResultsInUsingSecondCreators[] = {
+  &NeverFailsToFailSoundSystem::Create,
+  &NullSoundSystem::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, FailedFirstSystemResultsInUsingSecond) {
+  AutomaticallyChosenSoundSystem<
+      kFailedFirstSystemResultsInUsingSecondCreators,
+      ARRAY_SIZE(kFailedFirstSystemResultsInUsingSecondCreators)> sound_system;
+  EXPECT_TRUE(sound_system.Init());
+}
+
+extern const SoundSystemCreator kEarlierEntriesHavePriorityCreators[] = {
+  &InitCheckingSoundSystem1::Create,
+  &InitCheckingSoundSystem2::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, EarlierEntriesHavePriority) {
+  AutomaticallyChosenSoundSystem<
+      kEarlierEntriesHavePriorityCreators,
+      ARRAY_SIZE(kEarlierEntriesHavePriorityCreators)> sound_system;
+  InitCheckingSoundSystem1::created_ = false;
+  InitCheckingSoundSystem2::created_ = false;
+  EXPECT_TRUE(sound_system.Init());
+  EXPECT_TRUE(InitCheckingSoundSystem1::created_);
+  EXPECT_FALSE(InitCheckingSoundSystem2::created_);
+}
+
+extern const SoundSystemCreator kManySoundSystemsCreators[] = {
+  &NullSoundSystem::Create,
+  &NullSoundSystem::Create,
+  &NullSoundSystem::Create,
+  &NullSoundSystem::Create,
+  &NullSoundSystem::Create,
+  &NullSoundSystem::Create,
+  &NullSoundSystem::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, ManySoundSystems) {
+  AutomaticallyChosenSoundSystem<
+      kManySoundSystemsCreators,
+      ARRAY_SIZE(kManySoundSystemsCreators)> sound_system;
+  EXPECT_TRUE(sound_system.Init());
+}
+
+extern const SoundSystemCreator kDeletesAllCreatedSoundSystemsCreators[] = {
+  &DeletionCheckingSoundSystem1::Create,
+  &DeletionCheckingSoundSystem2::Create,
+  &DeletionCheckingSoundSystem3::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, DeletesAllCreatedSoundSystems) {
+  typedef AutomaticallyChosenSoundSystem<
+      kDeletesAllCreatedSoundSystemsCreators,
+      ARRAY_SIZE(kDeletesAllCreatedSoundSystemsCreators)> TestSoundSystem;
+  TestSoundSystem *sound_system = new TestSoundSystem();
+  DeletionCheckingSoundSystem1::deleted_ = false;
+  DeletionCheckingSoundSystem2::deleted_ = false;
+  DeletionCheckingSoundSystem3::deleted_ = false;
+  EXPECT_TRUE(sound_system->Init());
+  delete sound_system;
+  EXPECT_TRUE(DeletionCheckingSoundSystem1::deleted_);
+  EXPECT_TRUE(DeletionCheckingSoundSystem2::deleted_);
+  EXPECT_TRUE(DeletionCheckingSoundSystem3::deleted_);
+}
+
+}  // namespace cricket
diff --git a/talk/sound/pulseaudiosoundsystem.cc b/talk/sound/pulseaudiosoundsystem.cc
index 3b45f8c..4ccedc1 100644
--- a/talk/sound/pulseaudiosoundsystem.cc
+++ b/talk/sound/pulseaudiosoundsystem.cc
@@ -39,6 +39,7 @@
 #include "talk/base/fileutils.h"  // for GetApplicationName()
 #include "talk/base/logging.h"
 #include "talk/base/worker.h"
+#include "talk/base/timeutils.h"
 #include "talk/sound/sounddevicelocator.h"
 #include "talk/sound/soundinputstreaminterface.h"
 #include "talk/sound/soundoutputstreaminterface.h"
@@ -48,13 +49,6 @@
 // First PulseAudio protocol version that supports PA_STREAM_ADJUST_LATENCY.
 static const uint32_t kAdjustLatencyProtocolVersion = 13;
 
-// We define this flag if it's missing from our headers, because we want to be
-// able to compile against old headers but still use PA_STREAM_ADJUST_LATENCY
-// if run against a recent version of the library.
-#ifndef PA_STREAM_ADJUST_LATENCY
-#define PA_STREAM_ADJUST_LATENCY 0x2000U
-#endif
-
 // Lookup table from the cricket format enum in soundsysteminterface.h to
 // Pulse's enums.
 static const pa_sample_format_t kCricketFormatToPulseFormatTable[] = {
@@ -99,8 +93,6 @@
 // kNoLatencyRequirements case.)
 static const int kCaptureBufferExtraMsecs = 750;
 
-static const int kMsecsPerSec = 1000;
-
 static void FillPlaybackBufferAttr(int latency,
                                    pa_buffer_attr *attr) {
   attr->maxlength = latency;
@@ -909,7 +901,8 @@
     size_t bytes_per_sec = LATE(pa_bytes_per_second)(spec);
 
     int new_latency = configured_latency_ +
-        bytes_per_sec * kPlaybackLatencyIncrementMsecs / kMsecsPerSec;
+        bytes_per_sec * kPlaybackLatencyIncrementMsecs /
+        talk_base::kNumMicrosecsPerSec;
 
     pa_buffer_attr new_attr = {0};
     FillPlaybackBufferAttr(new_latency, &new_attr);
@@ -1468,7 +1461,8 @@
     latency = talk_base::_max(
         latency,
         static_cast<int>(
-            bytes_per_sec * kPlaybackLatencyMinimumMsecs / kMsecsPerSec));
+            bytes_per_sec * kPlaybackLatencyMinimumMsecs /
+            talk_base::kNumMicrosecsPerSec));
     FillPlaybackBufferAttr(latency, &attr);
     pattr = &attr;
   }
@@ -1498,13 +1492,14 @@
   if (latency != kNoLatencyRequirements) {
     size_t bytes_per_sec = LATE(pa_bytes_per_second)(&spec);
     if (latency == kLowLatency) {
-      latency = bytes_per_sec * kLowCaptureLatencyMsecs / kMsecsPerSec;
+      latency = bytes_per_sec * kLowCaptureLatencyMsecs /
+          talk_base::kNumMicrosecsPerSec;
     }
     // Note: fragsize specifies a maximum transfer size, not a minimum, so it is
     // not possible to force a high latency setting, only a low one.
     attr.fragsize = latency;
-    attr.maxlength = latency +
-        bytes_per_sec * kCaptureBufferExtraMsecs / kMsecsPerSec;
+    attr.maxlength = latency + bytes_per_sec * kCaptureBufferExtraMsecs /
+        talk_base::kNumMicrosecsPerSec;
     LOG(LS_VERBOSE) << "Configuring latency = " << attr.fragsize
                     << ", maxlength = " << attr.maxlength;
     pattr = &attr;
diff --git a/talk/xmllite/qname.cc b/talk/xmllite/qname.cc
index 7486524..0dadb79 100644
--- a/talk/xmllite/qname.cc
+++ b/talk/xmllite/qname.cc
@@ -25,138 +25,71 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <string>
-#include "talk/base/common.h"
-#include "talk/xmllite/xmlelement.h"
 #include "talk/xmllite/qname.h"
-#include "talk/xmllite/xmlconstants.h"
 
 namespace buzz {
 
-static int QName_Hash(const std::string & ns, const char * local) {
-  int result = static_cast<int>(ns.size()) * 101;
-  while (*local) {
-    result *= 19;
-    result += *local;
-    local += 1;
-  }
-  return result;
+QName::QName() {
 }
 
-static const int bits = 9;
-static QName::Data * get_qname_table() {
-  static QName::Data qname_table[1 << bits];
-  return qname_table;
+QName::QName(const QName& qname)
+    : namespace_(qname.namespace_),
+      local_part_(qname.local_part_) {
 }
 
-static QName::Data *
-AllocateOrFind(const std::string & ns, const char * local) {
-  int index = QName_Hash(ns, local);
-  int increment = index >> (bits - 1) | 1;
-  QName::Data * qname_table = get_qname_table();
-  for (;;) {
-    index &= ((1 << bits) - 1);
-    if (!qname_table[index].Occupied()) {
-      return new QName::Data(ns, local);
-    }
-    if (qname_table[index].localPart_ == local &&
-        qname_table[index].namespace_ == ns) {
-      qname_table[index].AddRef();
-      return qname_table + index;
-    }
-    index += increment;
-  }
+QName::QName(const StaticQName& const_value)
+    : namespace_(const_value.ns),
+      local_part_(const_value.local) {
 }
 
-static QName::Data *
-Add(const std::string & ns, const char * local) {
-  int index = QName_Hash(ns, local);
-  int increment = index >> (bits - 1) | 1;
-  QName::Data * qname_table = get_qname_table();
-  for (;;) {
-    index &= ((1 << bits) - 1);
-    if (!qname_table[index].Occupied()) {
-      qname_table[index].namespace_ = ns;
-      qname_table[index].localPart_ = local;
-      qname_table[index].AddRef(); // AddRef twice so it's never deleted
-      qname_table[index].AddRef();
-      return qname_table + index;
-    }
-    if (qname_table[index].localPart_ == local &&
-        qname_table[index].namespace_ == ns) {
-      qname_table[index].AddRef();
-      return qname_table + index;
-    }
-    index += increment;
+QName::QName(const std::string& ns, const std::string& local)
+    : namespace_(ns),
+      local_part_(local) {
+}
+
+QName::QName(const std::string& merged_or_local) {
+  size_t i = merged_or_local.rfind(':');
+  if (i == std::string::npos) {
+    local_part_ = merged_or_local;
+  } else {
+    namespace_ = merged_or_local.substr(0, i);
+    local_part_ = merged_or_local.substr(i + 1);
   }
 }
 
 QName::~QName() {
-  data_->Release();
 }
 
-QName::QName() : data_(QN_EMPTY.data_) {
-  data_->AddRef();
-}
+std::string QName::Merged() const {
+  if (namespace_[0] == '\0')
+    return local_part_;
 
-QName::QName(bool add, const std::string & ns, const char * local) :
-  data_(add ? Add(ns, local) : AllocateOrFind(ns, local)) {}
-
-QName::QName(bool add, const std::string & ns, const std::string & local) :
-  data_(add ? Add(ns, local.c_str()) : AllocateOrFind(ns, local.c_str())) {}
-
-QName::QName(const std::string & ns, const char * local) :
-  data_(AllocateOrFind(ns, local)) {}
-
-static std::string
-QName_LocalPart(const std::string & name) {
-  size_t i = name.rfind(':');
-  if (i == std::string::npos)
-    return name;
-  return name.substr(i + 1);
-}
-
-static std::string
-QName_Namespace(const std::string & name) {
-  size_t i = name.rfind(':');
-  if (i == std::string::npos)
-    return STR_EMPTY;
-  return name.substr(0, i);
-}
-
-QName::QName(const std::string & mergedOrLocal) :
-  data_(AllocateOrFind(QName_Namespace(mergedOrLocal),
-                 QName_LocalPart(mergedOrLocal).c_str())) {}
-
-std::string
-QName::Merged() const {
-  if (data_->namespace_ == STR_EMPTY)
-    return data_->localPart_;
-
-  std::string result(data_->namespace_);
-  result.reserve(result.length() + 1 + data_->localPart_.length());
+  std::string result;
+  result.reserve(namespace_.length() + 1 + local_part_.length());
+  result += namespace_;
   result += ':';
-  result += data_->localPart_;
+  result += local_part_;
   return result;
 }
 
-bool
-QName::operator==(const QName & other) const {
-  return other.data_ == data_ ||
-      (data_->localPart_ == other.data_->localPart_ &&
-       data_->namespace_ == other.data_->namespace_);
+bool QName::IsEmpty() const {
+  return namespace_.empty() && local_part_.empty();
 }
 
-int
-QName::Compare(const QName & other) const {
-  if (data_ == other.data_)
-    return 0;
-
-  int result = data_->localPart_.compare(other.data_->localPart_);
-  if (result)
+int QName::Compare(const StaticQName& other) const {
+  int result = local_part_.compare(other.local);
+  if (result != 0)
     return result;
 
-  return data_->namespace_.compare(other.data_->namespace_);
+  return namespace_.compare(other.ns);
 }
 
+int QName::Compare(const QName& other) const {
+  int result = local_part_.compare(other.local_part_);
+  if (result != 0)
+    return result;
+
+  return namespace_.compare(other.namespace_);
 }
+
+}  // namespace buzz
diff --git a/talk/xmllite/qname.h b/talk/xmllite/qname.h
index 172a067..92e54d0 100644
--- a/talk/xmllite/qname.h
+++ b/talk/xmllite/qname.h
@@ -2,87 +2,99 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef _qname_h_
-#define _qname_h_
+#ifndef TALK_XMLLITE_QNAME_H_
+#define TALK_XMLLITE_QNAME_H_
 
 #include <string>
-#include "talk/base/criticalsection.h"
 
 namespace buzz {
 
+class QName;
 
-class QName
-{
-public:
-  explicit QName();
-  QName(const QName & qname) : data_(qname.data_) { data_->AddRef(); }
-  explicit QName(bool add, const std::string & ns, const char * local);
-  explicit QName(bool add, const std::string & ns, const std::string & local);
-  explicit QName(const std::string & ns, const char * local);
-  explicit QName(const std::string & mergedOrLocal);
-  QName & operator=(const QName & qn) {
-    qn.data_->AddRef();
-    data_->Release();
-    data_ = qn.data_;
-    return *this;
-  }
-  ~QName();
+// StaticQName is used to represend constant quailified names. They
+// can be initialized statically and don't need intializers code, e.g.
+//   const StaticQName QN_FOO = { "foo_namespace", "foo" };
+//
+// Beside this use case, QName should be used everywhere
+// else. StaticQName instances are implicitly converted to QName
+// objects.
+struct StaticQName {
+  const char* const ns;
+  const char* const local;
 
-  const std::string & Namespace() const { return data_->namespace_; }
-  const std::string & LocalPart() const { return data_->localPart_; }
-  std::string Merged() const;
-  int Compare(const QName & other) const;
-  bool operator==(const QName & other) const;
-  bool operator!=(const QName & other) const { return !operator==(other); }
-  bool operator<(const QName & other) const { return Compare(other) < 0; }
-
-  class Data {
-  public:
-    Data(const std::string & ns, const std::string & local) :
-      namespace_(ns),
-      localPart_(local),
-      refcount_(1) {}
-
-    Data() : refcount_(0) {}
-
-    std::string namespace_;
-    std::string localPart_;
-    void AddRef() { talk_base::AtomicOps::Increment(&refcount_); }
-    void Release() { if (!talk_base::AtomicOps::Decrement(&refcount_)) { delete this; } }
-    bool Occupied() { return !!refcount_; }
-
-  private:
-    int refcount_;
-  };
-
-private:
-  Data * data_;
+  bool operator==(const QName& other) const;
+  bool operator!=(const QName& other) const;
 };
 
+class QName {
+ public:
+  QName();
+  QName(const QName& qname);
+  QName(const StaticQName& const_value);
+  QName(const std::string& ns, const std::string& local);
+  explicit QName(const std::string& merged_or_local);
+  ~QName();
 
+  const std::string& Namespace() const { return namespace_; }
+  const std::string& LocalPart() const { return local_part_; }
+  std::string Merged() const;
+  bool IsEmpty() const;
+
+  int Compare(const StaticQName& other) const;
+  int Compare(const QName& other) const;
+
+  bool operator==(const StaticQName& other) const {
+    return Compare(other) == 0;
+  }
+  bool operator==(const QName& other) const {
+    return Compare(other) == 0;
+  }
+  bool operator!=(const StaticQName& other) const {
+    return Compare(other) != 0;
+  }
+  bool operator!=(const QName& other) const {
+    return Compare(other) != 0;
+  }
+  bool operator<(const QName& other) const {
+    return Compare(other) < 0;
+  }
+
+ private:
+  std::string namespace_;
+  std::string local_part_;
+};
+
+inline bool StaticQName::operator==(const QName& other) const {
+  return other.Compare(*this) == 0;
 }
 
-#endif
+inline bool StaticQName::operator!=(const QName& other) const {
+  return other.Compare(*this) != 0;
+}
+
+}  // namespace buzz
+
+#endif  // TALK_XMLLITE_QNAME_H_
diff --git a/talk/xmllite/qname_unittest.cc b/talk/xmllite/qname_unittest.cc
new file mode 100644
index 0000000..976d822
--- /dev/null
+++ b/talk/xmllite/qname_unittest.cc
@@ -0,0 +1,131 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "talk/base/gunit.h"
+#include "talk/xmllite/qname.h"
+
+using buzz::StaticQName;
+using buzz::QName;
+
+TEST(QNameTest, TestTrivial) {
+  QName name("test");
+  EXPECT_EQ(name.LocalPart(), "test");
+  EXPECT_EQ(name.Namespace(), "");
+}
+
+TEST(QNameTest, TestSplit) {
+  QName name("a:test");
+  EXPECT_EQ(name.LocalPart(), "test");
+  EXPECT_EQ(name.Namespace(), "a");
+  QName name2("a-very:long:namespace:test-this");
+  EXPECT_EQ(name2.LocalPart(), "test-this");
+  EXPECT_EQ(name2.Namespace(), "a-very:long:namespace");
+}
+
+TEST(QNameTest, TestMerge) {
+  QName name("a", "test");
+  EXPECT_EQ(name.LocalPart(), "test");
+  EXPECT_EQ(name.Namespace(), "a");
+  EXPECT_EQ(name.Merged(), "a:test");
+  QName name2("a-very:long:namespace", "test-this");
+  EXPECT_EQ(name2.LocalPart(), "test-this");
+  EXPECT_EQ(name2.Namespace(), "a-very:long:namespace");
+  EXPECT_EQ(name2.Merged(), "a-very:long:namespace:test-this");
+}
+
+TEST(QNameTest, TestAssignment) {
+  QName name("a", "test");
+  // copy constructor
+  QName namecopy(name);
+  EXPECT_EQ(namecopy.LocalPart(), "test");
+  EXPECT_EQ(namecopy.Namespace(), "a");
+  QName nameassigned("");
+  nameassigned = name;
+  EXPECT_EQ(nameassigned.LocalPart(), "test");
+  EXPECT_EQ(nameassigned.Namespace(), "a");
+}
+
+TEST(QNameTest, TestConstAssignment) {
+  StaticQName name = { "a", "test" };
+  QName namecopy(name);
+  EXPECT_EQ(namecopy.LocalPart(), "test");
+  EXPECT_EQ(namecopy.Namespace(), "a");
+  QName nameassigned("");
+  nameassigned = name;
+  EXPECT_EQ(nameassigned.LocalPart(), "test");
+  EXPECT_EQ(nameassigned.Namespace(), "a");
+}
+
+TEST(QNameTest, TestEquality) {
+  QName name("a-very:long:namespace:test-this");
+  QName name2("a-very:long:namespace", "test-this");
+  QName name3("a-very:long:namespaxe", "test-this");
+  EXPECT_TRUE(name == name2);
+  EXPECT_FALSE(name == name3);
+}
+
+TEST(QNameTest, TestCompare) {
+  QName name("a");
+  QName name2("nsa", "a");
+  QName name3("nsa", "b");
+  QName name4("nsb", "b");
+
+  EXPECT_TRUE(name < name2);
+  EXPECT_FALSE(name2 < name);
+
+  EXPECT_FALSE(name2 < name2);
+
+  EXPECT_TRUE(name2 < name3);
+  EXPECT_FALSE(name3 < name2);
+
+  EXPECT_TRUE(name3 < name4);
+  EXPECT_FALSE(name4 < name3);
+}
+
+TEST(QNameTest, TestStaticQName) {
+  const StaticQName const_name1 = { "namespace", "local-name1" };
+  const StaticQName const_name2 = { "namespace", "local-name2" };
+  const QName name("namespace", "local-name1");
+  const QName name1 = const_name1;
+  const QName name2 = const_name2;
+
+  EXPECT_TRUE(name == const_name1);
+  EXPECT_TRUE(const_name1 == name);
+  EXPECT_FALSE(name != const_name1);
+  EXPECT_FALSE(const_name1 != name);
+
+  EXPECT_TRUE(name == name1);
+  EXPECT_TRUE(name1 == name);
+  EXPECT_FALSE(name != name1);
+  EXPECT_FALSE(name1 != name);
+
+  EXPECT_FALSE(name == name2);
+  EXPECT_FALSE(name2 == name);
+  EXPECT_TRUE(name != name2);
+  EXPECT_TRUE(name2 != name);
+}
diff --git a/talk/xmllite/xmlbuilder.cc b/talk/xmllite/xmlbuilder.cc
index 75daa57..486b6d5 100644
--- a/talk/xmllite/xmlbuilder.cc
+++ b/talk/xmllite/xmlbuilder.cc
@@ -30,6 +30,7 @@
 #include <vector>
 #include <set>
 #include "talk/base/common.h"
+#include "talk/xmllite/xmlconstants.h"
 #include "talk/xmllite/xmlelement.h"
 
 namespace buzz {
@@ -51,7 +52,7 @@
 XmlBuilder::BuildElement(XmlParseContext * pctx,
                               const char * name, const char ** atts) {
   QName tagName(pctx->ResolveQName(name, false));
-  if (tagName == QN_EMPTY)
+  if (tagName.IsEmpty())
     return NULL;
 
   XmlElement * pelNew = new XmlElement(tagName);
@@ -63,7 +64,7 @@
 
   while (*atts) {
     QName attName(pctx->ResolveQName(*atts, true));
-    if (attName == QN_EMPTY) {
+    if (attName.IsEmpty()) {
       delete pelNew;
       return NULL;
     }
@@ -143,6 +144,4 @@
 XmlBuilder::~XmlBuilder() {
 }
 
-
-
-}
+}  // namespace buzz
diff --git a/talk/xmllite/xmlbuilder_unittest.cc b/talk/xmllite/xmlbuilder_unittest.cc
new file mode 100644
index 0000000..9302276
--- /dev/null
+++ b/talk/xmllite/xmlbuilder_unittest.cc
@@ -0,0 +1,194 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlbuilder.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlparser.h"
+
+using buzz::XmlBuilder;
+using buzz::XmlElement;
+using buzz::XmlParser;
+
+TEST(XmlBuilderTest, TestTrivial) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing/>");
+  EXPECT_EQ("<testing/>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestAttributes1) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing a='b'/>");
+  EXPECT_EQ("<testing a=\"b\"/>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestAttributes2) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing e='' long='some text'/>");
+  EXPECT_EQ("<testing e=\"\" long=\"some text\"/>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestNesting1) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder,
+      "<top><first/><second><third></third></second></top>");
+  EXPECT_EQ("<top><first/><second><third/></second></top>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestNesting2) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder,
+    "<top><fifth><deeper><and><deeper/></and><sibling><leaf/>"
+    "</sibling></deeper></fifth><first/><second><third></third>"
+    "</second></top>");
+  EXPECT_EQ("<top><fifth><deeper><and><deeper/></and><sibling><leaf/>"
+    "</sibling></deeper></fifth><first/><second><third/>"
+    "</second></top>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestQuoting1) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing a='>'/>");
+  EXPECT_EQ("<testing a=\"&gt;\"/>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestQuoting2) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing a='&lt;>&amp;&quot;'/>");
+  EXPECT_EQ("<testing a=\"&lt;&gt;&amp;&quot;\"/>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestQuoting3) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing a='so &quot;important&quot;'/>");
+  EXPECT_EQ("<testing a=\"so &quot;important&quot;\"/>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestQuoting4) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing a='&quot;important&quot;, yes'/>");
+  EXPECT_EQ("<testing a=\"&quot;important&quot;, yes\"/>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestQuoting5) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder,
+      "<testing a='&lt;what is &quot;important&quot;&gt;'/>");
+  EXPECT_EQ("<testing a=\"&lt;what is &quot;important&quot;&gt;\"/>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestText1) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing>></testing>");
+  EXPECT_EQ("<testing>&gt;</testing>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestText2) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing>&lt;>&amp;&quot;</testing>");
+  EXPECT_EQ("<testing>&lt;&gt;&amp;\"</testing>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestText3) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing>so &lt;important&gt;</testing>");
+  EXPECT_EQ("<testing>so &lt;important&gt;</testing>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestText4) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing>&lt;important&gt;, yes</testing>");
+  EXPECT_EQ("<testing>&lt;important&gt;, yes</testing>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestText5) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder,
+      "<testing>importance &amp;&lt;important&gt;&amp;</testing>");
+  EXPECT_EQ("<testing>importance &amp;&lt;important&gt;&amp;</testing>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestNamespace1) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing xmlns='foo'/>");
+  EXPECT_EQ("<testing xmlns=\"foo\"/>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestNamespace2) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing xmlns:a='foo' a:b='c'/>");
+  EXPECT_EQ("<testing xmlns:a=\"foo\" a:b=\"c\"/>",
+      builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestNamespace3) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing xmlns:a=''/>");
+  EXPECT_TRUE(NULL == builder.BuiltElement());
+}
+
+TEST(XmlBuilderTest, TestNamespace4) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing a:b='c'/>");
+  EXPECT_TRUE(NULL == builder.BuiltElement());
+}
+
+TEST(XmlBuilderTest, TestAttrCollision1) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, "<testing a='first' a='second'/>");
+  EXPECT_TRUE(NULL == builder.BuiltElement());
+}
+
+TEST(XmlBuilderTest, TestAttrCollision2) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder,
+      "<testing xmlns:a='foo' xmlns:b='foo' a:x='c' b:x='d'/>");
+  EXPECT_TRUE(NULL == builder.BuiltElement());
+}
+
+TEST(XmlBuilderTest, TestAttrCollision3) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder,
+      "<testing xmlns:a='foo'><nested xmlns:b='foo' a:x='c' b:x='d'/>"
+      "</testing>");
+  EXPECT_TRUE(NULL == builder.BuiltElement());
+}
+
diff --git a/talk/xmllite/xmlconstants.cc b/talk/xmllite/xmlconstants.cc
index 503f832..f94d779 100644
--- a/talk/xmllite/xmlconstants.cc
+++ b/talk/xmllite/xmlconstants.cc
@@ -2,64 +2,41 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "xmlconstants.h"
+#include "talk/xmllite/xmlconstants.h"
 
-using namespace buzz;
+namespace buzz {
 
-const std::string & XmlConstants::str_empty() {
-  static const std::string str_empty_;
-  return str_empty_;
-}
+const char STR_EMPTY[] = "";
+const char NS_XML[] = "http://www.w3.org/XML/1998/namespace";
+const char NS_XMLNS[] = "http://www.w3.org/2000/xmlns/";
+const char STR_XMLNS[] = "xmlns";
+const char STR_XML[] = "xml";
+const char STR_VERSION[] = "version";
+const char STR_ENCODING[] = "encoding";
 
-const std::string & XmlConstants::ns_xml() {
-  static const std::string ns_xml_("http://www.w3.org/XML/1998/namespace");
-  return ns_xml_;
-}
+const StaticQName QN_XMLNS = { STR_EMPTY, STR_XMLNS };
 
-const std::string & XmlConstants::ns_xmlns() {
-  static const std::string ns_xmlns_("http://www.w3.org/2000/xmlns/");
-  return ns_xmlns_;
-}
-
-const std::string & XmlConstants::str_xmlns() {
-  static const std::string str_xmlns_("xmlns");
-  return str_xmlns_;
-}
-
-const std::string & XmlConstants::str_xml() {
-  static const std::string str_xml_("xml");
-  return str_xml_;
-}
-
-const std::string & XmlConstants::str_version() {
-  static const std::string str_version_("version");
-  return str_version_;
-}
-
-const std::string & XmlConstants::str_encoding() {
-  static const std::string str_encoding_("encoding");
-  return str_encoding_;
-}
+}  // namespace buzz
diff --git a/talk/xmllite/xmlconstants.h b/talk/xmllite/xmlconstants.h
index 8514d6f..3e5da98 100644
--- a/talk/xmllite/xmlconstants.h
+++ b/talk/xmllite/xmlconstants.h
@@ -2,60 +2,46 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-// Because global constant initialization order is undefined
-// globals cannot depend on other objects to be instantiated.
-// This class creates string objects within static methods 
-// such that globals may refer to these constants by the
-// accessor function and they are guaranteed to be initialized.
+#ifndef TALK_XMLLITE_XMLCONSTANTS_H_
+#define TALK_XMLLITE_XMLCONSTANTS_H_
 
-#ifndef TALK_XMLLITE_CONSTANTS_H_
-#define TALK_XMLLITE_CONSTANTS_H_
+#include "talk/xmllite/qname.h"
 
-#include <string>
-
-#define STR_EMPTY    XmlConstants::str_empty()
-#define NS_XML       XmlConstants::ns_xml()
-#define NS_XMLNS     XmlConstants::ns_xmlns()
-#define STR_XMLNS    XmlConstants::str_xmlns()
-#define STR_XML      XmlConstants::str_xml()
-#define STR_VERSION  XmlConstants::str_version()
-#define STR_ENCODING XmlConstants::str_encoding()
 namespace buzz {
-	
-class XmlConstants {
- public:
-  static const std::string & str_empty();
-  static const std::string & ns_xml();
-  static const std::string & ns_xmlns();
-  static const std::string & str_xmlns();
-  static const std::string & str_xml();
-  static const std::string & str_version();
-  static const std::string & str_encoding();
-};
 
-}
+extern const char STR_EMPTY[];
+extern const char NS_XML[];
+extern const char NS_XMLNS[];
+extern const char STR_XMLNS[];
+extern const char STR_XML[];
+extern const char STR_VERSION[];
+extern const char STR_ENCODING[];
 
-#endif  // TALK_XMLLITE_CONSTANTS_H_
+extern const StaticQName QN_XMLNS;
+
+}  // namespace buzz
+
+#endif  // TALK_XMLLITE_XMLCONSTANTS_H_
diff --git a/talk/xmllite/xmlelement.cc b/talk/xmllite/xmlelement.cc
index bf436fb..176ce5c 100644
--- a/talk/xmllite/xmlelement.cc
+++ b/talk/xmllite/xmlelement.cc
@@ -25,12 +25,14 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "talk/xmllite/xmlelement.h"
+
+#include <ostream>
+#include <sstream>
 #include <string>
 #include <vector>
-#include <sstream>
 
 #include "talk/base/common.h"
-#include "talk/xmllite/xmlelement.h"
 #include "talk/xmllite/qname.h"
 #include "talk/xmllite/xmlparser.h"
 #include "talk/xmllite/xmlbuilder.h"
@@ -39,242 +41,235 @@
 
 namespace buzz {
 
-const QName QN_EMPTY(true, STR_EMPTY, STR_EMPTY);
-const QName QN_XMLNS(true, STR_EMPTY, STR_XMLNS);
-
-
 XmlChild::~XmlChild() {
 }
 
-bool
-XmlText::IsTextImpl() const {
+bool XmlText::IsTextImpl() const {
   return true;
 }
 
-XmlElement *
-XmlText::AsElementImpl() const {
+XmlElement* XmlText::AsElementImpl() const {
   return NULL;
 }
 
-XmlText *
-XmlText::AsTextImpl() const {
+XmlText* XmlText::AsTextImpl() const {
   return const_cast<XmlText *>(this);
 }
 
-void
-XmlText::SetText(const std::string & text) {
+void XmlText::SetText(const std::string& text) {
   text_ = text;
 }
 
-void
-XmlText::AddParsedText(const char * buf, int len) {
+void XmlText::AddParsedText(const char* buf, int len) {
   text_.append(buf, len);
 }
 
-void
-XmlText::AddText(const std::string & text) {
+void XmlText::AddText(const std::string& text) {
   text_ += text;
 }
 
 XmlText::~XmlText() {
 }
 
-XmlElement::XmlElement(const QName & name) :
+XmlElement::XmlElement(const QName& name) :
     name_(name),
-    pFirstAttr_(NULL),
-    pLastAttr_(NULL),
-    pFirstChild_(NULL),
-    pLastChild_(NULL),
+    first_attr_(NULL),
+    last_attr_(NULL),
+    first_child_(NULL),
+    last_child_(NULL),
     cdata_(false) {
 }
 
-XmlElement::XmlElement(const XmlElement & elt) :
+XmlElement::XmlElement(const XmlElement& elt) :
     XmlChild(),
     name_(elt.name_),
-    pFirstAttr_(NULL),
-    pLastAttr_(NULL),
-    pFirstChild_(NULL),
-    pLastChild_(NULL),
+    first_attr_(NULL),
+    last_attr_(NULL),
+    first_child_(NULL),
+    last_child_(NULL),
     cdata_(false) {
 
   // copy attributes
-  XmlAttr * pAttr;
-  XmlAttr ** ppLastAttr = &pFirstAttr_;
-  XmlAttr * newAttr = NULL;
-  for (pAttr = elt.pFirstAttr_; pAttr; pAttr = pAttr->NextAttr()) {
-    newAttr = new XmlAttr(*pAttr);
-    *ppLastAttr = newAttr;
-    ppLastAttr = &(newAttr->pNextAttr_);
+  XmlAttr* attr;
+  XmlAttr ** plast_attr = &first_attr_;
+  XmlAttr* newAttr = NULL;
+  for (attr = elt.first_attr_; attr; attr = attr->NextAttr()) {
+    newAttr = new XmlAttr(*attr);
+    *plast_attr = newAttr;
+    plast_attr = &(newAttr->next_attr_);
   }
-  pLastAttr_ = newAttr;
+  last_attr_ = newAttr;
 
   // copy children
-  XmlChild * pChild;
-  XmlChild ** ppLast = &pFirstChild_;
-  XmlChild * newChild = NULL;
+  XmlChild* pChild;
+  XmlChild ** ppLast = &first_child_;
+  XmlChild* newChild = NULL;
 
-  for (pChild = elt.pFirstChild_; pChild; pChild = pChild->NextChild()) {
+  for (pChild = elt.first_child_; pChild; pChild = pChild->NextChild()) {
     if (pChild->IsText()) {
       newChild = new XmlText(*(pChild->AsText()));
     } else {
       newChild = new XmlElement(*(pChild->AsElement()));
     }
     *ppLast = newChild;
-    ppLast = &(newChild->pNextChild_);
+    ppLast = &(newChild->next_child_);
   }
-  pLastChild_ = newChild;
+  last_child_ = newChild;
 
   cdata_ = elt.cdata_;
 }
 
-XmlElement::XmlElement(const QName & name, bool useDefaultNs) :
+XmlElement::XmlElement(const QName& name, bool useDefaultNs) :
   name_(name),
-  pFirstAttr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL),
-  pLastAttr_(pFirstAttr_),
-  pFirstChild_(NULL),
-  pLastChild_(NULL),
+  first_attr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL),
+  last_attr_(first_attr_),
+  first_child_(NULL),
+  last_child_(NULL),
   cdata_(false) {
 }
 
-bool
-XmlElement::IsTextImpl() const {
+bool XmlElement::IsTextImpl() const {
   return false;
 }
 
-XmlElement *
-XmlElement::AsElementImpl() const {
+XmlElement* XmlElement::AsElementImpl() const {
   return const_cast<XmlElement *>(this);
 }
 
-XmlText *
-XmlElement::AsTextImpl() const {
+XmlText* XmlElement::AsTextImpl() const {
   return NULL;
 }
 
-const std::string &
-XmlElement::BodyText() const {
-  if (pFirstChild_ && pFirstChild_->IsText() && pLastChild_ == pFirstChild_) {
-    return pFirstChild_->AsText()->Text();
+const std::string XmlElement::BodyText() const {
+  if (first_child_ && first_child_->IsText() && last_child_ == first_child_) {
+    return first_child_->AsText()->Text();
   }
 
-  return STR_EMPTY;
+  return std::string();
 }
 
-void
-XmlElement::SetBodyText(const std::string & text) {
-  if (text == STR_EMPTY) {
+void XmlElement::SetBodyText(const std::string& text) {
+  if (text.empty()) {
     ClearChildren();
-  } else if (pFirstChild_ == NULL) {
+  } else if (first_child_ == NULL) {
     AddText(text);
-  } else if (pFirstChild_->IsText() && pLastChild_ == pFirstChild_) {
-    pFirstChild_->AsText()->SetText(text);
+  } else if (first_child_->IsText() && last_child_ == first_child_) {
+    first_child_->AsText()->SetText(text);
   } else {
     ClearChildren();
     AddText(text);
   }
 }
 
-const QName &
-XmlElement::FirstElementName() const {
-  const XmlElement * element = FirstElement();
+const QName XmlElement::FirstElementName() const {
+  const XmlElement* element = FirstElement();
   if (element == NULL)
-    return QN_EMPTY;
+    return QName();
   return element->Name();
 }
 
-XmlAttr *
-XmlElement::FirstAttr() {
-  return pFirstAttr_;
+XmlAttr* XmlElement::FirstAttr() {
+  return first_attr_;
 }
 
-const std::string &
-XmlElement::Attr(const QName & name) const {
-  XmlAttr * pattr;
-  for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
-    if (pattr->name_ == name)
-      return pattr->value_;
+const std::string XmlElement::Attr(const StaticQName& name) const {
+  XmlAttr* attr;
+  for (attr = first_attr_; attr; attr = attr->next_attr_) {
+    if (attr->name_ == name)
+      return attr->value_;
   }
-  return STR_EMPTY;
+  return std::string();
 }
 
-bool
-XmlElement::HasAttr(const QName & name) const {
-  XmlAttr * pattr;
-  for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
-    if (pattr->name_ == name)
+const std::string XmlElement::Attr(const QName& name) const {
+  XmlAttr* attr;
+  for (attr = first_attr_; attr; attr = attr->next_attr_) {
+    if (attr->name_ == name)
+      return attr->value_;
+  }
+  return std::string();
+}
+
+bool XmlElement::HasAttr(const StaticQName& name) const {
+  XmlAttr* attr;
+  for (attr = first_attr_; attr; attr = attr->next_attr_) {
+    if (attr->name_ == name)
       return true;
   }
   return false;
 }
 
-void
-XmlElement::SetAttr(const QName & name, const std::string & value) {
-  XmlAttr * pattr;
-  for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
-    if (pattr->name_ == name)
+bool XmlElement::HasAttr(const QName& name) const {
+  XmlAttr* attr;
+  for (attr = first_attr_; attr; attr = attr->next_attr_) {
+    if (attr->name_ == name)
+      return true;
+  }
+  return false;
+}
+
+void XmlElement::SetAttr(const QName& name, const std::string& value) {
+  XmlAttr* attr;
+  for (attr = first_attr_; attr; attr = attr->next_attr_) {
+    if (attr->name_ == name)
       break;
   }
-  if (!pattr) {
-    pattr = new XmlAttr(name, value);
-    if (pLastAttr_)
-      pLastAttr_->pNextAttr_ = pattr;
+  if (!attr) {
+    attr = new XmlAttr(name, value);
+    if (last_attr_)
+      last_attr_->next_attr_ = attr;
     else
-      pFirstAttr_ = pattr;
-    pLastAttr_ = pattr;
+      first_attr_ = attr;
+    last_attr_ = attr;
     return;
   }
-  pattr->value_ = value;
+  attr->value_ = value;
 }
 
-void
-XmlElement::ClearAttr(const QName & name) {
-  XmlAttr * pattr;
-  XmlAttr *pLastAttr = NULL;
-  for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
-    if (pattr->name_ == name)
+void XmlElement::ClearAttr(const QName& name) {
+  XmlAttr* attr;
+  XmlAttr* last_attr = NULL;
+  for (attr = first_attr_; attr; attr = attr->next_attr_) {
+    if (attr->name_ == name)
       break;
-    pLastAttr = pattr;
+    last_attr = attr;
   }
-  if (!pattr)
+  if (!attr)
     return;
-  if (!pLastAttr)
-    pFirstAttr_ = pattr->pNextAttr_;
+  if (!last_attr)
+    first_attr_ = attr->next_attr_;
   else
-    pLastAttr->pNextAttr_ = pattr->pNextAttr_;
-  if (pLastAttr_ == pattr)
-    pLastAttr_ = pLastAttr;
-  delete pattr;
+    last_attr->next_attr_ = attr->next_attr_;
+  if (last_attr_ == attr)
+    last_attr_ = last_attr;
+  delete attr;
 }
 
-XmlChild *
-XmlElement::FirstChild() {
-  return pFirstChild_;
+XmlChild* XmlElement::FirstChild() {
+  return first_child_;
 }
 
-XmlElement *
-XmlElement::FirstElement() {
-  XmlChild * pChild;
-  for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement* XmlElement::FirstElement() {
+  XmlChild* pChild;
+  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
     if (!pChild->IsText())
       return pChild->AsElement();
   }
   return NULL;
 }
 
-XmlElement *
-XmlElement::NextElement() {
-  XmlChild * pChild;
-  for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement* XmlElement::NextElement() {
+  XmlChild* pChild;
+  for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
     if (!pChild->IsText())
       return pChild->AsElement();
   }
   return NULL;
 }
 
-XmlElement *
-XmlElement::FirstWithNamespace(const std::string & ns) {
-  XmlChild * pChild;
-  for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement* XmlElement::FirstWithNamespace(const std::string& ns) {
+  XmlChild* pChild;
+  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
     if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
       return pChild->AsElement();
   }
@@ -282,9 +277,9 @@
 }
 
 XmlElement *
-XmlElement::NextWithNamespace(const std::string & ns) {
-  XmlChild * pChild;
-  for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement::NextWithNamespace(const std::string& ns) {
+  XmlChild* pChild;
+  for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
     if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
       return pChild->AsElement();
   }
@@ -292,9 +287,9 @@
 }
 
 XmlElement *
-XmlElement::FirstNamed(const QName & name) {
-  XmlChild * pChild;
-  for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement::FirstNamed(const QName& name) {
+  XmlChild* pChild;
+  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
     if (!pChild->IsText() && pChild->AsElement()->Name() == name)
       return pChild->AsElement();
   }
@@ -302,9 +297,29 @@
 }
 
 XmlElement *
-XmlElement::NextNamed(const QName & name) {
-  XmlChild * pChild;
-  for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement::FirstNamed(const StaticQName& name) {
+  XmlChild* pChild;
+  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
+    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+      return pChild->AsElement();
+  }
+  return NULL;
+}
+
+XmlElement *
+XmlElement::NextNamed(const QName& name) {
+  XmlChild* pChild;
+  for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
+    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+      return pChild->AsElement();
+  }
+  return NULL;
+}
+
+XmlElement *
+XmlElement::NextNamed(const StaticQName& name) {
+  XmlChild* pChild;
+  for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
     if (!pChild->IsText() && pChild->AsElement()->Name() == name)
       return pChild->AsElement();
   }
@@ -321,132 +336,121 @@
   return child;
 }
 
-const std::string &
-XmlElement::TextNamed(const QName & name) const {
-  XmlChild * pChild;
-  for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+const std::string XmlElement::TextNamed(const QName& name) const {
+  XmlChild* pChild;
+  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
     if (!pChild->IsText() && pChild->AsElement()->Name() == name)
       return pChild->AsElement()->BodyText();
   }
-  return STR_EMPTY;
+  return std::string();
 }
 
-void
-XmlElement::InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNext) {
-  if (pPredecessor == NULL) {
-    pNext->pNextChild_ = pFirstChild_;
-    pFirstChild_ = pNext;
+void XmlElement::InsertChildAfter(XmlChild* predecessor, XmlChild* next) {
+  if (predecessor == NULL) {
+    next->next_child_ = first_child_;
+    first_child_ = next;
   }
   else {
-    pNext->pNextChild_ = pPredecessor->pNextChild_;
-    pPredecessor->pNextChild_ = pNext;
+    next->next_child_ = predecessor->next_child_;
+    predecessor->next_child_ = next;
   }
 }
 
-void
-XmlElement::RemoveChildAfter(XmlChild * pPredecessor) {
-  XmlChild * pNext;
+void XmlElement::RemoveChildAfter(XmlChild* predecessor) {
+  XmlChild* next;
 
-  if (pPredecessor == NULL) {
-    pNext = pFirstChild_;
-    pFirstChild_ = pNext->pNextChild_;
+  if (predecessor == NULL) {
+    next = first_child_;
+    first_child_ = next->next_child_;
   }
   else {
-    pNext = pPredecessor->pNextChild_;
-    pPredecessor->pNextChild_ = pNext->pNextChild_;
+    next = predecessor->next_child_;
+    predecessor->next_child_ = next->next_child_;
   }
 
-  if (pLastChild_ == pNext)
-    pLastChild_ = pPredecessor;
+  if (last_child_ == next)
+    last_child_ = predecessor;
 
-  delete pNext;
+  delete next;
 }
 
-void
-XmlElement::AddAttr(const QName & name, const std::string & value) {
+void XmlElement::AddAttr(const QName& name, const std::string& value) {
   ASSERT(!HasAttr(name));
 
-  XmlAttr ** pprev = pLastAttr_ ? &(pLastAttr_->pNextAttr_) : &pFirstAttr_;
-  pLastAttr_ = (*pprev = new XmlAttr(name, value));
+  XmlAttr ** pprev = last_attr_ ? &(last_attr_->next_attr_) : &first_attr_;
+  last_attr_ = (*pprev = new XmlAttr(name, value));
 }
 
-void
-XmlElement::AddAttr(const QName & name, const std::string & value,
+void XmlElement::AddAttr(const QName& name, const std::string& value,
                          int depth) {
-  XmlElement * element = this;
+  XmlElement* element = this;
   while (depth--) {
-    element = element->pLastChild_->AsElement();
+    element = element->last_child_->AsElement();
   }
   element->AddAttr(name, value);
 }
 
-void
-XmlElement::AddParsedText(const char * cstr, int len) {
+void XmlElement::AddParsedText(const char* cstr, int len) {
   if (len == 0)
     return;
 
-  if (pLastChild_ && pLastChild_->IsText()) {
-    pLastChild_->AsText()->AddParsedText(cstr, len);
+  if (last_child_ && last_child_->IsText()) {
+    last_child_->AsText()->AddParsedText(cstr, len);
     return;
   }
-  XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
-  pLastChild_ = *pprev = new XmlText(cstr, len);
+  XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_;
+  last_child_ = *pprev = new XmlText(cstr, len);
 }
 
-void
-XmlElement::AddCDATAText(const char * buf, int len) {
+void XmlElement::AddCDATAText(const char* buf, int len) {
   cdata_ = true;
   AddParsedText(buf, len);
 }
 
-void
-XmlElement::AddText(const std::string & text) {
+void XmlElement::AddText(const std::string& text) {
   if (text == STR_EMPTY)
     return;
 
-  if (pLastChild_ && pLastChild_->IsText()) {
-    pLastChild_->AsText()->AddText(text);
+  if (last_child_ && last_child_->IsText()) {
+    last_child_->AsText()->AddText(text);
     return;
   }
-  XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
-  pLastChild_ = *pprev = new XmlText(text);
+  XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_;
+  last_child_ = *pprev = new XmlText(text);
 }
 
-void
-XmlElement::AddText(const std::string & text, int depth) {
+void XmlElement::AddText(const std::string& text, int depth) {
   // note: the first syntax is ambigious for msvc 6
-  // XmlElement * pel(this);
-  XmlElement * element = this;
+  // XmlElement* pel(this);
+  XmlElement* element = this;
   while (depth--) {
-    element = element->pLastChild_->AsElement();
+    element = element->last_child_->AsElement();
   }
   element->AddText(text);
 }
 
-void
-XmlElement::AddElement(XmlElement *pelChild) {
-  if (pelChild == NULL)
+void XmlElement::AddElement(XmlElement *child) {
+  if (child == NULL)
     return;
 
-  XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
-  pLastChild_ = *pprev = pelChild;
-  pelChild->pNextChild_ = NULL;
+  XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_;
+  *pprev = child;
+  last_child_ = child;
+  child->next_child_ = NULL;
 }
 
-void
-XmlElement::AddElement(XmlElement *pelChild, int depth) {
-  XmlElement * element = this;
+void XmlElement::AddElement(XmlElement *child, int depth) {
+  XmlElement* element = this;
   while (depth--) {
-    element = element->pLastChild_->AsElement();
+    element = element->last_child_->AsElement();
   }
-  element->AddElement(pelChild);
+  element->AddElement(child);
 }
 
-void
-XmlElement::ClearNamedChildren(const QName & name) {
-  XmlChild * prev_child = NULL;
-  XmlChild * next_child;
-  XmlChild * child;
+void XmlElement::ClearNamedChildren(const QName& name) {
+  XmlChild* prev_child = NULL;
+  XmlChild* next_child;
+  XmlChild* child;
   for (child = FirstChild(); child; child = next_child) {
     next_child = child->NextChild();
     if (!child->IsText() && child->AsElement()->Name() == name)
@@ -458,62 +462,52 @@
   }
 }
 
-void
-XmlElement::ClearAttributes() {
-  XmlAttr * pattr;
-  for (pattr = pFirstAttr_; pattr; ) {
-    XmlAttr * pToDelete = pattr;
-    pattr = pattr->pNextAttr_;
-    delete pToDelete;
+void XmlElement::ClearAttributes() {
+  XmlAttr* attr;
+  for (attr = first_attr_; attr; ) {
+    XmlAttr* to_delete = attr;
+    attr = attr->next_attr_;
+    delete to_delete;
   }
-  pFirstAttr_ = pLastAttr_ = NULL;
+  first_attr_ = last_attr_ = NULL;
 }
 
-void
-XmlElement::ClearChildren() {
-  XmlChild * pchild;
-  for (pchild = pFirstChild_; pchild; ) {
-    XmlChild * pToDelete = pchild;
-    pchild = pchild->pNextChild_;
-    delete pToDelete;
+void XmlElement::ClearChildren() {
+  XmlChild* pchild;
+  for (pchild = first_child_; pchild; ) {
+    XmlChild* to_delete = pchild;
+    pchild = pchild->next_child_;
+    delete to_delete;
   }
-  pFirstChild_ = pLastChild_ = NULL;
+  first_child_ = last_child_ = NULL;
 }
 
-std::string
-XmlElement::Str() const {
+std::string XmlElement::Str() const {
   std::stringstream ss;
-  Print(&ss, NULL, 0);
+  XmlPrinter::PrintXml(&ss, this);
   return ss.str();
 }
 
-XmlElement *
-XmlElement::ForStr(const std::string & str) {
+XmlElement* XmlElement::ForStr(const std::string& str) {
   XmlBuilder builder;
   XmlParser::ParseXml(&builder, str);
   return builder.CreateElement();
 }
 
-void
-XmlElement::Print(
-    std::ostream * pout, std::string xmlns[], int xmlnsCount) const {
-  XmlPrinter::PrintXml(pout, this, xmlns, xmlnsCount);
-}
-
 XmlElement::~XmlElement() {
-  XmlAttr * pattr;
-  for (pattr = pFirstAttr_; pattr; ) {
-    XmlAttr * pToDelete = pattr;
-    pattr = pattr->pNextAttr_;
-    delete pToDelete;
+  XmlAttr* attr;
+  for (attr = first_attr_; attr; ) {
+    XmlAttr* to_delete = attr;
+    attr = attr->next_attr_;
+    delete to_delete;
   }
 
-  XmlChild * pchild;
-  for (pchild = pFirstChild_; pchild; ) {
-    XmlChild * pToDelete = pchild;
-    pchild = pchild->pNextChild_;
-    delete pToDelete;
+  XmlChild* pchild;
+  for (pchild = first_child_; pchild; ) {
+    XmlChild* to_delete = pchild;
+    pchild = pchild->next_child_;
+    delete to_delete;
   }
 }
 
-}
+}  // namespace buzz
diff --git a/talk/xmllite/xmlelement.h b/talk/xmllite/xmlelement.h
index 38e31a9..ffdc333 100644
--- a/talk/xmllite/xmlelement.h
+++ b/talk/xmllite/xmlelement.h
@@ -2,238 +2,250 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef _xmlelement_h_
-#define _xmlelement_h_
+#ifndef TALK_XMLLITE_XMLELEMENT_H_
+#define TALK_XMLLITE_XMLELEMENT_H_
 
 #include <iosfwd>
 #include <string>
+
 #include "talk/base/scoped_ptr.h"
 #include "talk/xmllite/qname.h"
 
 namespace buzz {
 
-extern const QName QN_EMPTY;
-extern const QName QN_XMLNS;
-
-
 class XmlChild;
 class XmlText;
 class XmlElement;
 class XmlAttr;
 
 class XmlChild {
-friend class XmlElement;
-
-public:
-
-  XmlChild * NextChild() { return pNextChild_; }
-  const XmlChild * NextChild() const { return pNextChild_; }
+ public:
+  XmlChild* NextChild() { return next_child_; }
+  const XmlChild* NextChild() const { return next_child_; }
 
   bool IsText() const { return IsTextImpl(); }
 
-  XmlElement * AsElement() { return AsElementImpl(); }
-  const XmlElement * AsElement() const { return AsElementImpl(); }
+  XmlElement* AsElement() { return AsElementImpl(); }
+  const XmlElement* AsElement() const { return AsElementImpl(); }
 
-  XmlText * AsText() { return AsTextImpl(); }
-  const XmlText * AsText() const { return AsTextImpl(); }
+  XmlText* AsText() { return AsTextImpl(); }
+  const XmlText* AsText() const { return AsTextImpl(); }
 
 
-protected:
-
+ protected:
   XmlChild() :
-    pNextChild_(NULL) {
+    next_child_(NULL) {
   }
 
   virtual bool IsTextImpl() const = 0;
-  virtual XmlElement * AsElementImpl() const = 0;
-  virtual XmlText * AsTextImpl() const = 0;
+  virtual XmlElement* AsElementImpl() const = 0;
+  virtual XmlText* AsTextImpl() const = 0;
 
 
   virtual ~XmlChild();
 
-private:
-  XmlChild(const XmlChild & noimpl);
+ private:
+  friend class XmlElement;
 
-  XmlChild * pNextChild_;
+  XmlChild(const XmlChild& noimpl);
 
+  XmlChild* next_child_;
 };
 
 class XmlText : public XmlChild {
-public:
-  explicit XmlText(const std::string & text) :
+ public:
+  explicit XmlText(const std::string& text) :
     XmlChild(),
     text_(text) {
   }
-  explicit XmlText(const XmlText & t) :
+  explicit XmlText(const XmlText& t) :
     XmlChild(),
     text_(t.text_) {
   }
-  explicit XmlText(const char * cstr, size_t len) :
+  explicit XmlText(const char* cstr, size_t len) :
     XmlChild(),
     text_(cstr, len) {
   }
   virtual ~XmlText();
 
-  const std::string & Text() const { return text_; }
-  void SetText(const std::string & text);
-  void AddParsedText(const char * buf, int len);
-  void AddText(const std::string & text);
+  const std::string& Text() const { return text_; }
+  void SetText(const std::string& text);
+  void AddParsedText(const char* buf, int len);
+  void AddText(const std::string& text);
 
-protected:
+ protected:
   virtual bool IsTextImpl() const;
-  virtual XmlElement * AsElementImpl() const;
-  virtual XmlText * AsTextImpl() const;
+  virtual XmlElement* AsElementImpl() const;
+  virtual XmlText* AsTextImpl() const;
 
-private:
+ private:
   std::string text_;
 };
 
 class XmlAttr {
-friend class XmlElement;
+ public:
+  XmlAttr* NextAttr() const { return next_attr_; }
+  const QName& Name() const { return name_; }
+  const std::string& Value() const { return value_; }
 
-public:
-  XmlAttr * NextAttr() const { return pNextAttr_; }
-  const QName & Name() const { return name_; }
-  const std::string & Value() const { return value_; }
+ private:
+  friend class XmlElement;
 
-private:
-  explicit XmlAttr(const QName & name, const std::string & value) :
-    pNextAttr_(NULL),
+  explicit XmlAttr(const QName& name, const std::string& value) :
+    next_attr_(NULL),
     name_(name),
     value_(value) {
   }
-  explicit XmlAttr(const XmlAttr & att) :
-    pNextAttr_(NULL),
+  explicit XmlAttr(const XmlAttr& att) :
+    next_attr_(NULL),
     name_(att.name_),
     value_(att.value_) {
   }
 
-  XmlAttr * pNextAttr_;
+  XmlAttr* next_attr_;
   QName name_;
   std::string value_;
 };
 
 class XmlElement : public XmlChild {
-public:
-  explicit XmlElement(const QName & name);
-  explicit XmlElement(const QName & name, bool useDefaultNs);
-  explicit XmlElement(const XmlElement & elt);
+ public:
+  explicit XmlElement(const QName& name);
+  explicit XmlElement(const QName& name, bool useDefaultNs);
+  explicit XmlElement(const XmlElement& elt);
 
   virtual ~XmlElement();
 
   const QName& Name() const { return name_; }
   void SetName(const QName& name) { name_ = name; }
 
-  const std::string & BodyText() const;
-  void SetBodyText(const std::string & text);
+  const std::string BodyText() const;
+  void SetBodyText(const std::string& text);
 
-  const QName & FirstElementName() const;
+  const QName FirstElementName() const;
 
-  XmlAttr * FirstAttr();
-  const XmlAttr * FirstAttr() const
+  XmlAttr* FirstAttr();
+  const XmlAttr* FirstAttr() const
     { return const_cast<XmlElement *>(this)->FirstAttr(); }
 
-  //! Attr will return STR_EMPTY if the attribute isn't there:
-  //! use HasAttr to test presence of an attribute. 
-  const std::string & Attr(const QName & name) const;
-  bool HasAttr(const QName & name) const;
-  void SetAttr(const QName & name, const std::string & value);
-  void ClearAttr(const QName & name);
+  // Attr will return an empty string if the attribute isn't there:
+  // use HasAttr to test presence of an attribute.
+  const std::string Attr(const StaticQName& name) const;
+  const std::string Attr(const QName& name) const;
+  bool HasAttr(const StaticQName& name) const;
+  bool HasAttr(const QName& name) const;
+  void SetAttr(const QName& name, const std::string& value);
+  void ClearAttr(const QName& name);
 
-  XmlChild * FirstChild();
-  const XmlChild * FirstChild() const
-    { return const_cast<XmlElement *>(this)->FirstChild(); }
+  XmlChild* FirstChild();
+  const XmlChild* FirstChild() const {
+    return const_cast<XmlElement *>(this)->FirstChild();
+  }
 
-  XmlElement * FirstElement();
-  const XmlElement * FirstElement() const
-    { return const_cast<XmlElement *>(this)->FirstElement(); }
+  XmlElement* FirstElement();
+  const XmlElement* FirstElement() const {
+    return const_cast<XmlElement *>(this)->FirstElement();
+  }
 
-  XmlElement * NextElement();
-  const XmlElement * NextElement() const
-    { return const_cast<XmlElement *>(this)->NextElement(); }
+  XmlElement* NextElement();
+  const XmlElement* NextElement() const {
+    return const_cast<XmlElement *>(this)->NextElement();
+  }
 
-  XmlElement * FirstWithNamespace(const std::string & ns);
-  const XmlElement * FirstWithNamespace(const std::string & ns) const
-    { return const_cast<XmlElement *>(this)->FirstWithNamespace(ns); }
+  XmlElement* FirstWithNamespace(const std::string& ns);
+  const XmlElement* FirstWithNamespace(const std::string& ns) const {
+    return const_cast<XmlElement *>(this)->FirstWithNamespace(ns);
+  }
 
-  XmlElement * NextWithNamespace(const std::string & ns);
-  const XmlElement * NextWithNamespace(const std::string & ns) const
-    { return const_cast<XmlElement *>(this)->NextWithNamespace(ns); }
+  XmlElement* NextWithNamespace(const std::string& ns);
+  const XmlElement* NextWithNamespace(const std::string& ns) const {
+    return const_cast<XmlElement *>(this)->NextWithNamespace(ns);
+  }
 
-  XmlElement * FirstNamed(const QName & name);
-  const XmlElement * FirstNamed(const QName & name) const
-    { return const_cast<XmlElement *>(this)->FirstNamed(name); }
+  XmlElement* FirstNamed(const StaticQName& name);
+  const XmlElement* FirstNamed(const StaticQName& name) const {
+    return const_cast<XmlElement *>(this)->FirstNamed(name);
+  }
 
-  XmlElement * NextNamed(const QName & name);
-  const XmlElement * NextNamed(const QName & name) const
-    { return const_cast<XmlElement *>(this)->NextNamed(name); }
+  XmlElement* FirstNamed(const QName& name);
+  const XmlElement* FirstNamed(const QName& name) const {
+    return const_cast<XmlElement *>(this)->FirstNamed(name);
+  }
+
+  XmlElement* NextNamed(const StaticQName& name);
+  const XmlElement* NextNamed(const StaticQName& name) const {
+    return const_cast<XmlElement *>(this)->NextNamed(name);
+  }
+
+  XmlElement* NextNamed(const QName& name);
+  const XmlElement* NextNamed(const QName& name) const {
+    return const_cast<XmlElement *>(this)->NextNamed(name);
+  }
 
   // Finds the first element named 'name'.  If that element can't be found then
   // adds one and returns it.
   XmlElement* FindOrAddNamedChild(const QName& name);
 
-  const std::string & TextNamed(const QName & name) const;
+  const std::string TextNamed(const QName& name) const;
 
-  void InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNewChild);
-  void RemoveChildAfter(XmlChild * pPredecessor);
+  void InsertChildAfter(XmlChild* predecessor, XmlChild* new_child);
+  void RemoveChildAfter(XmlChild* predecessor);
 
-  void AddParsedText(const char * buf, int len);
+  void AddParsedText(const char* buf, int len);
   // Note: CDATA is not supported by XMPP, therefore using this function will
   // generate non-XMPP compatible XML.
-  void AddCDATAText(const char * buf, int len);
-  void AddText(const std::string & text);
-  void AddText(const std::string & text, int depth);
-  void AddElement(XmlElement * pelChild);
-  void AddElement(XmlElement * pelChild, int depth);
-  void AddAttr(const QName & name, const std::string & value);
-  void AddAttr(const QName & name, const std::string & value, int depth);
-  void ClearNamedChildren(const QName & name);
+  void AddCDATAText(const char* buf, int len);
+  void AddText(const std::string& text);
+  void AddText(const std::string& text, int depth);
+  void AddElement(XmlElement* child);
+  void AddElement(XmlElement* child, int depth);
+  void AddAttr(const QName& name, const std::string& value);
+  void AddAttr(const QName& name, const std::string& value, int depth);
+  void ClearNamedChildren(const QName& name);
   void ClearAttributes();
   void ClearChildren();
 
-  static XmlElement * ForStr(const std::string & str);
+  static XmlElement* ForStr(const std::string& str);
   std::string Str() const;
 
-  void Print(std::ostream * pout, std::string xmlns[], int xmlnsCount) const;
-
   bool IsCDATA() const { return cdata_; }
 
-protected:
+ protected:
   virtual bool IsTextImpl() const;
-  virtual XmlElement * AsElementImpl() const;
-  virtual XmlText * AsTextImpl() const;
+  virtual XmlElement* AsElementImpl() const;
+  virtual XmlText* AsTextImpl() const;
 
-private:
+ private:
   QName name_;
-  XmlAttr * pFirstAttr_;
-  XmlAttr * pLastAttr_;
-  XmlChild * pFirstChild_;
-  XmlChild * pLastChild_;
+  XmlAttr* first_attr_;
+  XmlAttr* last_attr_;
+  XmlChild* first_child_;
+  XmlChild* last_child_;
   bool cdata_;
 };
 
-}
-#endif
+}  // namespace buzz
+
+#endif  // TALK_XMLLITE_XMLELEMENT_H_
diff --git a/talk/xmllite/xmlelement_unittest.cc b/talk/xmllite/xmlelement_unittest.cc
new file mode 100644
index 0000000..6d488fa
--- /dev/null
+++ b/talk/xmllite/xmlelement_unittest.cc
@@ -0,0 +1,271 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/xmllite/xmlelement.h"
+
+using buzz::QName;
+using buzz::XmlAttr;
+using buzz::XmlChild;
+using buzz::XmlElement;
+
+std::ostream& operator<<(std::ostream& os, const QName& name) {
+  os << name.Namespace() << ":" << name.LocalPart();
+  return os;
+}
+
+TEST(XmlElementTest, TestConstructors) {
+  XmlElement elt(QName("google:test", "first"));
+  EXPECT_EQ("<test:first xmlns:test=\"google:test\"/>", elt.Str());
+
+  XmlElement elt2(QName("google:test", "first"), true);
+  EXPECT_EQ("<first xmlns=\"google:test\"/>", elt2.Str());
+}
+
+TEST(XmlElementTest, TestAdd) {
+  XmlElement elt(QName("google:test", "root"), true);
+  elt.AddElement(new XmlElement(QName("google:test", "first")));
+  elt.AddElement(new XmlElement(QName("google:test", "nested")), 1);
+  elt.AddText("nested-value", 2);
+  elt.AddText("between-", 1);
+  elt.AddText("value", 1);
+  elt.AddElement(new XmlElement(QName("google:test", "nested2")), 1);
+  elt.AddElement(new XmlElement(QName("google:test", "second")));
+  elt.AddText("init-value", 1);
+  elt.AddElement(new XmlElement(QName("google:test", "nested3")), 1);
+  elt.AddText("trailing-value", 1);
+
+  // make sure it looks ok overall
+  EXPECT_EQ("<root xmlns=\"google:test\">"
+        "<first><nested>nested-value</nested>between-value<nested2/></first>"
+        "<second>init-value<nested3/>trailing-value</second></root>",
+        elt.Str());
+
+  // make sure text was concatenated
+  XmlChild * pchild =
+    elt.FirstChild()->AsElement()->FirstChild()->NextChild();
+  EXPECT_TRUE(pchild->IsText());
+  EXPECT_EQ("between-value", pchild->AsText()->Text());
+}
+
+TEST(XmlElementTest, TestAttrs) {
+  XmlElement elt(QName("", "root"));
+  elt.SetAttr(QName("", "a"), "avalue");
+  EXPECT_EQ("<root a=\"avalue\"/>", elt.Str());
+
+  elt.SetAttr(QName("", "b"), "bvalue");
+  EXPECT_EQ("<root a=\"avalue\" b=\"bvalue\"/>", elt.Str());
+
+  elt.SetAttr(QName("", "a"), "avalue2");
+  EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue\"/>", elt.Str());
+
+  elt.SetAttr(QName("", "b"), "bvalue2");
+  EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\"/>", elt.Str());
+
+  elt.SetAttr(QName("", "c"), "cvalue");
+  EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\" c=\"cvalue\"/>", elt.Str());
+
+  XmlAttr * patt = elt.FirstAttr();
+  EXPECT_EQ(QName("", "a"), patt->Name());
+  EXPECT_EQ("avalue2", patt->Value());
+
+  patt = patt->NextAttr();
+  EXPECT_EQ(QName("", "b"), patt->Name());
+  EXPECT_EQ("bvalue2", patt->Value());
+
+  patt = patt->NextAttr();
+  EXPECT_EQ(QName("", "c"), patt->Name());
+  EXPECT_EQ("cvalue", patt->Value());
+
+  patt = patt->NextAttr();
+  EXPECT_TRUE(NULL == patt);
+
+  EXPECT_TRUE(elt.HasAttr(QName("", "a")));
+  EXPECT_TRUE(elt.HasAttr(QName("", "b")));
+  EXPECT_TRUE(elt.HasAttr(QName("", "c")));
+  EXPECT_FALSE(elt.HasAttr(QName("", "d")));
+
+  elt.SetAttr(QName("", "d"), "dvalue");
+  EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\" c=\"cvalue\" d=\"dvalue\"/>",
+      elt.Str());
+  EXPECT_TRUE(elt.HasAttr(QName("", "d")));
+
+  elt.ClearAttr(QName("", "z"));  // not found, no effect
+  EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\" c=\"cvalue\" d=\"dvalue\"/>",
+      elt.Str());
+
+  elt.ClearAttr(QName("", "b"));
+  EXPECT_EQ("<root a=\"avalue2\" c=\"cvalue\" d=\"dvalue\"/>", elt.Str());
+
+  elt.ClearAttr(QName("", "a"));
+  EXPECT_EQ("<root c=\"cvalue\" d=\"dvalue\"/>", elt.Str());
+
+  elt.ClearAttr(QName("", "d"));
+  EXPECT_EQ("<root c=\"cvalue\"/>", elt.Str());
+
+  elt.ClearAttr(QName("", "c"));
+  EXPECT_EQ("<root/>", elt.Str());
+}
+
+TEST(XmlElementTest, TestBodyText) {
+  XmlElement elt(QName("", "root"));
+  EXPECT_EQ("", elt.BodyText());
+
+  elt.AddText("body value text");
+
+  EXPECT_EQ("body value text", elt.BodyText());
+
+  elt.ClearChildren();
+  elt.AddText("more value ");
+  elt.AddText("text");
+
+  EXPECT_EQ("more value text", elt.BodyText());
+
+  elt.ClearChildren();
+  elt.AddText("decoy");
+  elt.AddElement(new XmlElement(QName("", "dummy")));
+  EXPECT_EQ("", elt.BodyText());
+
+  elt.SetBodyText("replacement");
+  EXPECT_EQ("replacement", elt.BodyText());
+
+  elt.SetBodyText("");
+  EXPECT_TRUE(NULL == elt.FirstChild());
+
+  elt.SetBodyText("goodbye");
+  EXPECT_EQ("goodbye", elt.FirstChild()->AsText()->Text());
+  EXPECT_EQ("goodbye", elt.BodyText());
+}
+
+TEST(XmlElementTest, TestCopyConstructor) {
+  XmlElement * element = XmlElement::ForStr(
+      "<root xmlns='test-foo'>This is a <em a='avalue' b='bvalue'>"
+      "little <b>little</b></em> test</root>");
+
+  XmlElement * pelCopy = new XmlElement(*element);
+  EXPECT_EQ("<root xmlns=\"test-foo\">This is a <em a=\"avalue\" b=\"bvalue\">"
+      "little <b>little</b></em> test</root>", pelCopy->Str());
+  delete pelCopy;
+
+  pelCopy = new XmlElement(*(element->FirstChild()->NextChild()->AsElement()));
+  EXPECT_EQ("<foo:em a=\"avalue\" b=\"bvalue\" xmlns:foo=\"test-foo\">"
+      "little <foo:b>little</foo:b></foo:em>", pelCopy->Str());
+
+  XmlAttr * patt = pelCopy->FirstAttr();
+  EXPECT_EQ(QName("", "a"), patt->Name());
+  EXPECT_EQ("avalue", patt->Value());
+
+  patt = patt->NextAttr();
+  EXPECT_EQ(QName("", "b"), patt->Name());
+  EXPECT_EQ("bvalue", patt->Value());
+
+  patt = patt->NextAttr();
+  EXPECT_TRUE(NULL == patt);
+  delete pelCopy;
+  delete element;
+}
+
+TEST(XmlElementTest, TestNameSearch) {
+  XmlElement * element = XmlElement::ForStr(
+    "<root xmlns='test-foo'>"
+      "<firstname>George</firstname>"
+      "<middlename>X.</middlename>"
+      "some text"
+      "<lastname>Harrison</lastname>"
+      "<firstname>John</firstname>"
+      "<middlename>Y.</middlename>"
+      "<lastname>Lennon</lastname>"
+    "</root>");
+  EXPECT_TRUE(NULL ==
+      element->FirstNamed(QName("", "firstname")));
+  EXPECT_EQ(element->FirstChild(),
+      element->FirstNamed(QName("test-foo", "firstname")));
+  EXPECT_EQ(element->FirstChild()->NextChild(),
+      element->FirstNamed(QName("test-foo", "middlename")));
+  EXPECT_EQ(element->FirstElement()->NextElement(),
+      element->FirstNamed(QName("test-foo", "middlename")));
+  EXPECT_EQ("Harrison",
+      element->TextNamed(QName("test-foo", "lastname")));
+  EXPECT_EQ(element->FirstElement()->NextElement()->NextElement(),
+      element->FirstNamed(QName("test-foo", "lastname")));
+  EXPECT_EQ("John", element->FirstNamed(QName("test-foo", "firstname"))->
+      NextNamed(QName("test-foo", "firstname"))->BodyText());
+  EXPECT_EQ("Y.", element->FirstNamed(QName("test-foo", "middlename"))->
+      NextNamed(QName("test-foo", "middlename"))->BodyText());
+  EXPECT_EQ("Lennon", element->FirstNamed(QName("test-foo", "lastname"))->
+      NextNamed(QName("test-foo", "lastname"))->BodyText());
+  EXPECT_TRUE(NULL == element->FirstNamed(QName("test-foo", "firstname"))->
+      NextNamed(QName("test-foo", "firstname"))->
+      NextNamed(QName("test-foo", "firstname")));
+
+  delete element;
+}
+
+class XmlElementCreatorThread : public talk_base::Thread {
+ public:
+  XmlElementCreatorThread(int count, buzz::QName qname) :
+      count_(count), qname_(qname) {}
+
+  virtual void Run() {
+    std::vector<buzz::XmlElement*> elems;
+    for (int i = 0; i < count_; i++) {
+      elems.push_back(new XmlElement(qname_));
+    }
+    for (int i = 0; i < count_; i++) {
+      delete elems[i];
+    }
+  }
+
+ private:
+  int count_;
+  buzz::QName qname_;
+};
+
+// If XmlElement creation and destruction isn't thread safe,
+// this test should crash.
+TEST(XmlElementTest, TestMultithread) {
+  int thread_count = 2;  // Was 100, but that's too slow.
+  int elem_count = 100;  // Was 100000, but that's too slow.
+  buzz::QName qname("foo", "bar");
+
+  std::vector<talk_base::Thread*> threads;
+  for (int i = 0; i < thread_count; i++) {
+    threads.push_back(
+        new XmlElementCreatorThread(elem_count, qname));
+    threads[i]->Start();
+  }
+
+  for (int i = 0; i < thread_count; i++) {
+    threads[i]->Stop();
+    delete threads[i];
+  }
+}
diff --git a/talk/xmllite/xmlnsstack.cc b/talk/xmllite/xmlnsstack.cc
index b2bf370..26e27f8 100644
--- a/talk/xmllite/xmlnsstack.cc
+++ b/talk/xmllite/xmlnsstack.cc
@@ -25,11 +25,13 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "talk/xmllite/xmlnsstack.h"
+
+#include <sstream>
 #include <string>
 #include <vector>
-#include <sstream>
+
 #include "talk/xmllite/xmlelement.h"
-#include "talk/xmllite/xmlnsstack.h"
 #include "talk/xmllite/xmlconstants.h"
 
 namespace buzz {
@@ -41,13 +43,11 @@
 
 XmlnsStack::~XmlnsStack() {}
 
-void
-XmlnsStack::PushFrame() {
+void XmlnsStack::PushFrame() {
   pxmlnsDepthStack_->push_back(pxmlnsStack_->size());
 }
 
-void
-XmlnsStack::PopFrame() {
+void XmlnsStack::PopFrame() {
   size_t prev_size = pxmlnsDepthStack_->back();
   pxmlnsDepthStack_->pop_back();
   if (prev_size < pxmlnsStack_->size()) {
@@ -56,42 +56,41 @@
   }
 }
 
-const std::string *
-XmlnsStack::NsForPrefix(const std::string & prefix) {
+std::pair<std::string, bool> XmlnsStack::NsForPrefix(
+    const std::string& prefix) {
   if (prefix.length() >= 3 &&
       (prefix[0] == 'x' || prefix[0] == 'X') &&
       (prefix[1] == 'm' || prefix[1] == 'M') &&
       (prefix[2] == 'l' || prefix[2] == 'L')) {
     if (prefix == "xml")
-      return &(NS_XML);
+      return std::make_pair(NS_XML, true);
     if (prefix == "xmlns")
-      return &(NS_XMLNS);
-    return NULL;
+      return std::make_pair(NS_XMLNS, true);
+    // Other names with xml prefix are illegal.
+    return std::make_pair(STR_EMPTY, false);
   }
 
   std::vector<std::string>::iterator pos;
   for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) {
     pos -= 2;
     if (*pos == prefix)
-      return &(*(pos + 1));
+      return std::make_pair(*(pos + 1), true);
   }
 
   if (prefix == STR_EMPTY)
-    return &(STR_EMPTY); // default namespace
+    return std::make_pair(STR_EMPTY, true);  // default namespace
 
-  return NULL; // none found
+  return std::make_pair(STR_EMPTY, false);  // none found
 }
 
-bool
-XmlnsStack::PrefixMatchesNs(const std::string & prefix, const std::string & ns) {
-  const std::string * match = NsForPrefix(prefix);
-  if (match == NULL)
-    return false;
-  return (*match == ns);
+bool XmlnsStack::PrefixMatchesNs(const std::string& prefix,
+                                 const std::string& ns) {
+  const std::pair<std::string, bool> match = NsForPrefix(prefix);
+  return match.second && (match.first == ns);
 }
 
-std::pair<std::string, bool>
-XmlnsStack::PrefixForNs(const std::string & ns, bool isattr) {
+std::pair<std::string, bool> XmlnsStack::PrefixForNs(const std::string& ns,
+                                                     bool isattr) {
   if (ns == NS_XML)
     return std::make_pair(std::string("xml"), true);
   if (ns == NS_XMLNS)
@@ -110,8 +109,7 @@
   return std::make_pair(STR_EMPTY, false); // none found
 }
 
-std::string
-XmlnsStack::FormatQName(const QName & name, bool isAttr) {
+std::string XmlnsStack::FormatQName(const QName& name, bool isAttr) {
   std::string prefix(PrefixForNs(name.Namespace(), isAttr).first);
   if (prefix == STR_EMPTY)
     return name.LocalPart();
@@ -119,14 +117,12 @@
     return prefix + ':' + name.LocalPart();
 }
 
-void
-XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) {
+void XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) {
   pxmlnsStack_->push_back(prefix);
   pxmlnsStack_->push_back(ns);
 }
 
-void
-XmlnsStack::RemoveXmlns() {
+void XmlnsStack::RemoveXmlns() {
   pxmlnsStack_->pop_back();
   pxmlnsStack_->pop_back();
 }
@@ -173,16 +169,15 @@
   return "ns";
 }
 
-
-std::pair<std::string, bool>
-XmlnsStack::AddNewPrefix(const std::string & ns, bool isAttr) {
+std::pair<std::string, bool> XmlnsStack::AddNewPrefix(const std::string& ns,
+                                                      bool isAttr) {
   if (PrefixForNs(ns, isAttr).second)
     return std::make_pair(STR_EMPTY, false);
 
   std::string base(SuggestPrefix(ns));
   std::string result(base);
   int i = 2;
-  while (NsForPrefix(result) != NULL) {
+  while (NsForPrefix(result).second) {
     std::stringstream ss;
     ss << base;
     ss << (i++);
diff --git a/talk/xmllite/xmlnsstack.h b/talk/xmllite/xmlnsstack.h
index c7e9f89..f6b4b81 100644
--- a/talk/xmllite/xmlnsstack.h
+++ b/talk/xmllite/xmlnsstack.h
@@ -25,8 +25,8 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef _xmlnsstack_h_
-#define _xmlnsstack_h_
+#ifndef TALK_XMLLITE_XMLNSSTACK_H_
+#define TALK_XMLLITE_XMLNSSTACK_H_
 
 #include <string>
 #include <vector>
@@ -40,16 +40,16 @@
   XmlnsStack();
   ~XmlnsStack();
 
-  void AddXmlns(const std::string & prefix, const std::string & ns);
+  void AddXmlns(const std::string& prefix, const std::string& ns);
   void RemoveXmlns();
   void PushFrame();
   void PopFrame();
   void Reset();
 
-  const std::string * NsForPrefix(const std::string & prefix);
+  std::pair<std::string, bool> NsForPrefix(const std::string& prefix);
   bool PrefixMatchesNs(const std::string & prefix, const std::string & ns);
-  std::pair<std::string, bool> PrefixForNs(const std::string & ns, bool isAttr);
-  std::pair<std::string, bool> AddNewPrefix(const std::string & ns, bool isAttr);
+  std::pair<std::string, bool> PrefixForNs(const std::string& ns, bool isAttr);
+  std::pair<std::string, bool> AddNewPrefix(const std::string& ns, bool isAttr);
   std::string FormatQName(const QName & name, bool isAttr);
 
 private:
@@ -59,4 +59,4 @@
 };
 }
 
-#endif
+#endif  // TALK_XMLLITE_XMLNSSTACK_H_
diff --git a/talk/xmllite/xmlnsstack_unittest.cc b/talk/xmllite/xmlnsstack_unittest.cc
new file mode 100644
index 0000000..21e157b
--- /dev/null
+++ b/talk/xmllite/xmlnsstack_unittest.cc
@@ -0,0 +1,258 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmllite/xmlnsstack.h"
+
+#include <string>
+#include <sstream>
+#include <iostream>
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlconstants.h"
+
+using buzz::NS_XML;
+using buzz::NS_XMLNS;
+using buzz::QName;
+using buzz::XmlnsStack;
+
+TEST(XmlnsStackTest, TestBuiltin) {
+  XmlnsStack stack;
+
+  EXPECT_EQ(std::string(NS_XML), stack.NsForPrefix("xml").first);
+  EXPECT_EQ(std::string(NS_XMLNS), stack.NsForPrefix("xmlns").first);
+  EXPECT_EQ("", stack.NsForPrefix("").first);
+
+  EXPECT_EQ("xml", stack.PrefixForNs(NS_XML, false).first);
+  EXPECT_EQ("xmlns", stack.PrefixForNs(NS_XMLNS, false).first);
+  EXPECT_EQ("", stack.PrefixForNs("", false).first);
+  EXPECT_EQ("", stack.PrefixForNs("", true).first);
+}
+
+TEST(XmlnsStackTest, TestNsForPrefix) {
+ XmlnsStack stack;
+  stack.AddXmlns("pre1", "ns1");
+  stack.AddXmlns("pre2", "ns2");
+  stack.AddXmlns("pre1", "ns3");
+  stack.AddXmlns("", "ns4");
+
+  EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first);
+  EXPECT_TRUE(stack.NsForPrefix("pre1").second);
+  EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first);
+  EXPECT_EQ("ns4", stack.NsForPrefix("").first);
+  EXPECT_EQ("", stack.NsForPrefix("pre3").first);
+  EXPECT_EQ(false, stack.NsForPrefix("pre3").second);
+}
+
+TEST(XmlnsStackTest, TestPrefixForNs) {
+  XmlnsStack stack;
+  stack.AddXmlns("pre1", "ns1");
+  stack.AddXmlns("pre2", "ns2");
+  stack.AddXmlns("pre1", "ns3");
+  stack.AddXmlns("pre3", "ns2");
+  stack.AddXmlns("pre4", "ns4");
+  stack.AddXmlns("", "ns4");
+
+  EXPECT_EQ("", stack.PrefixForNs("ns1", false).first);
+  EXPECT_FALSE(stack.PrefixForNs("ns1", false).second);
+  EXPECT_EQ("", stack.PrefixForNs("ns1", true).first);
+  EXPECT_FALSE(stack.PrefixForNs("ns1", true).second);
+  EXPECT_EQ("pre3", stack.PrefixForNs("ns2", false).first);
+  EXPECT_TRUE(stack.PrefixForNs("ns2", false).second);
+  EXPECT_EQ("pre3", stack.PrefixForNs("ns2", true).first);
+  EXPECT_TRUE(stack.PrefixForNs("ns2", true).second);
+  EXPECT_EQ("pre1", stack.PrefixForNs("ns3", false).first);
+  EXPECT_EQ("pre1", stack.PrefixForNs("ns3", true).first);
+  EXPECT_EQ("", stack.PrefixForNs("ns4", false).first);
+  EXPECT_TRUE(stack.PrefixForNs("ns4", false).second);
+  EXPECT_EQ("pre4", stack.PrefixForNs("ns4", true).first);
+  EXPECT_EQ("", stack.PrefixForNs("ns5", false).first);
+  EXPECT_FALSE(stack.PrefixForNs("ns5", false).second);
+  EXPECT_EQ("", stack.PrefixForNs("ns5", true).first);
+  EXPECT_EQ("", stack.PrefixForNs("", false).first);
+  EXPECT_EQ("", stack.PrefixForNs("", true).first);
+
+  stack.AddXmlns("", "ns6");
+  EXPECT_EQ("", stack.PrefixForNs("ns6", false).first);
+  EXPECT_TRUE(stack.PrefixForNs("ns6", false).second);
+  EXPECT_EQ("", stack.PrefixForNs("ns6", true).first);
+  EXPECT_FALSE(stack.PrefixForNs("ns6", true).second);
+}
+
+TEST(XmlnsStackTest, TestFrames) {
+  XmlnsStack stack;
+  stack.PushFrame();
+  stack.AddXmlns("pre1", "ns1");
+  stack.AddXmlns("pre2", "ns2");
+
+  stack.PushFrame();
+  stack.AddXmlns("pre1", "ns3");
+  stack.AddXmlns("pre3", "ns2");
+  stack.AddXmlns("pre4", "ns4");
+
+  stack.PushFrame();
+  stack.PushFrame();
+  stack.AddXmlns("", "ns4");
+
+  // basic test
+  EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first);
+  EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first);
+  EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first);
+  EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first);
+  EXPECT_EQ("", stack.NsForPrefix("pre5").first);
+  EXPECT_FALSE(stack.NsForPrefix("pre5").second);
+  EXPECT_EQ("ns4", stack.NsForPrefix("").first);
+  EXPECT_TRUE(stack.NsForPrefix("").second);
+
+  // pop the default xmlns definition
+  stack.PopFrame();
+  EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first);
+  EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first);
+  EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first);
+  EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first);
+  EXPECT_EQ("", stack.NsForPrefix("pre5").first);
+  EXPECT_FALSE(stack.NsForPrefix("pre5").second);
+  EXPECT_EQ("", stack.NsForPrefix("").first);
+  EXPECT_TRUE(stack.NsForPrefix("").second);
+
+  // pop empty frame (nop)
+  stack.PopFrame();
+  EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first);
+  EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first);
+  EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first);
+  EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first);
+  EXPECT_EQ("", stack.NsForPrefix("pre5").first);
+  EXPECT_FALSE(stack.NsForPrefix("pre5").second);
+  EXPECT_EQ("", stack.NsForPrefix("").first);
+  EXPECT_TRUE(stack.NsForPrefix("").second);
+
+  // pop frame with three defs
+  stack.PopFrame();
+  EXPECT_EQ("ns1", stack.NsForPrefix("pre1").first);
+  EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first);
+  EXPECT_EQ("", stack.NsForPrefix("pre3").first);
+  EXPECT_FALSE(stack.NsForPrefix("pre3").second);
+  EXPECT_EQ("", stack.NsForPrefix("pre4").first);
+  EXPECT_FALSE(stack.NsForPrefix("pre4").second);
+  EXPECT_EQ("", stack.NsForPrefix("pre5").first);
+  EXPECT_FALSE(stack.NsForPrefix("pre5").second);
+  EXPECT_EQ("", stack.NsForPrefix("").first);
+  EXPECT_TRUE(stack.NsForPrefix("").second);
+
+  // pop frame with last two defs
+  stack.PopFrame();
+  EXPECT_FALSE(stack.NsForPrefix("pre1").second);
+  EXPECT_FALSE(stack.NsForPrefix("pre2").second);
+  EXPECT_FALSE(stack.NsForPrefix("pre3").second);
+  EXPECT_FALSE(stack.NsForPrefix("pre4").second);
+  EXPECT_FALSE(stack.NsForPrefix("pre5").second);
+  EXPECT_TRUE(stack.NsForPrefix("").second);
+  EXPECT_EQ("", stack.NsForPrefix("pre1").first);
+  EXPECT_EQ("", stack.NsForPrefix("pre2").first);
+  EXPECT_EQ("", stack.NsForPrefix("pre3").first);
+  EXPECT_EQ("", stack.NsForPrefix("pre4").first);
+  EXPECT_EQ("", stack.NsForPrefix("pre5").first);
+  EXPECT_EQ("", stack.NsForPrefix("").first);
+}
+
+TEST(XmlnsStackTest, TestAddNewPrefix) {
+  XmlnsStack stack;
+
+  // builtin namespaces cannot be added
+  EXPECT_FALSE(stack.AddNewPrefix("", true).second);
+  EXPECT_FALSE(stack.AddNewPrefix("", false).second);
+  EXPECT_FALSE(stack.AddNewPrefix(NS_XML, true).second);
+  EXPECT_FALSE(stack.AddNewPrefix(NS_XML, false).second);
+  EXPECT_FALSE(stack.AddNewPrefix(NS_XMLNS, true).second);
+  EXPECT_FALSE(stack.AddNewPrefix(NS_XMLNS, false).second);
+
+  // namespaces already added cannot be added again.
+  EXPECT_EQ("foo", stack.AddNewPrefix("http://a.b.com/foo.htm", true).first);
+  EXPECT_EQ("bare", stack.AddNewPrefix("http://a.b.com/bare", false).first);
+  EXPECT_EQ("z", stack.AddNewPrefix("z", false).first);
+  EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/foo.htm", true).second);
+  EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/bare", true).second);
+  EXPECT_FALSE(stack.AddNewPrefix("z", true).second);
+  EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/foo.htm", false).second);
+  EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/bare", false).second);
+  EXPECT_FALSE(stack.AddNewPrefix("z", false).second);
+
+  // default namespace usable by non-attributes only
+  stack.AddXmlns("", "http://my/default");
+  EXPECT_FALSE(stack.AddNewPrefix("http://my/default", false).second);
+  EXPECT_EQ("def", stack.AddNewPrefix("http://my/default", true).first);
+
+  // namespace cannot start with 'xml'
+  EXPECT_EQ("ns", stack.AddNewPrefix("http://a.b.com/xmltest", true).first);
+  EXPECT_EQ("ns2", stack.AddNewPrefix("xmlagain", false).first);
+
+  // verify added namespaces are still defined
+  EXPECT_EQ("http://a.b.com/foo.htm", stack.NsForPrefix("foo").first);
+  EXPECT_TRUE(stack.NsForPrefix("foo").second);
+  EXPECT_EQ("http://a.b.com/bare", stack.NsForPrefix("bare").first);
+  EXPECT_TRUE(stack.NsForPrefix("bare").second);
+  EXPECT_EQ("z", stack.NsForPrefix("z").first);
+  EXPECT_TRUE(stack.NsForPrefix("z").second);
+  EXPECT_EQ("http://my/default", stack.NsForPrefix("").first);
+  EXPECT_TRUE(stack.NsForPrefix("").second);
+  EXPECT_EQ("http://my/default", stack.NsForPrefix("def").first);
+  EXPECT_TRUE(stack.NsForPrefix("def").second);
+  EXPECT_EQ("http://a.b.com/xmltest", stack.NsForPrefix("ns").first);
+  EXPECT_TRUE(stack.NsForPrefix("ns").second);
+  EXPECT_EQ("xmlagain", stack.NsForPrefix("ns2").first);
+  EXPECT_TRUE(stack.NsForPrefix("ns2").second);
+}
+
+TEST(XmlnsStackTest, TestFormatQName) {
+  XmlnsStack stack;
+  stack.AddXmlns("pre1", "ns1");
+  stack.AddXmlns("pre2", "ns2");
+  stack.AddXmlns("pre1", "ns3");
+  stack.AddXmlns("", "ns4");
+
+  EXPECT_EQ("zip",
+      stack.FormatQName(QName("ns1", "zip"), false));  // no match
+  EXPECT_EQ("pre2:abracadabra",
+      stack.FormatQName(QName("ns2", "abracadabra"), false));
+  EXPECT_EQ("pre1:a",
+      stack.FormatQName(QName("ns3", "a"), false));
+  EXPECT_EQ("simple",
+      stack.FormatQName(QName("ns4", "simple"), false));
+  EXPECT_EQ("root",
+      stack.FormatQName(QName("", "root"), false));  // no match
+
+  EXPECT_EQ("zip",
+      stack.FormatQName(QName("ns1", "zip"), true));  // no match
+  EXPECT_EQ("pre2:abracadabra",
+      stack.FormatQName(QName("ns2", "abracadabra"), true));
+  EXPECT_EQ("pre1:a",
+      stack.FormatQName(QName("ns3", "a"), true));
+  EXPECT_EQ("simple",
+      stack.FormatQName(QName("ns4", "simple"), true));  // no match
+  EXPECT_EQ("root",
+      stack.FormatQName(QName("", "root"), true));
+}
diff --git a/talk/xmllite/xmlparser.cc b/talk/xmllite/xmlparser.cc
index b267414..3e4d733 100644
--- a/talk/xmllite/xmlparser.cc
+++ b/talk/xmllite/xmlparser.cc
@@ -29,6 +29,7 @@
 
 #include <string>
 #include <vector>
+
 #include "talk/base/common.h"
 #include "talk/xmllite/xmlconstants.h"
 #include "talk/xmllite/xmlelement.h"
@@ -206,12 +207,7 @@
 
 void
 XmlParser::ParseContext::StartNamespace(const char *prefix, const char *ns) {
-  xmlnsstack_.AddXmlns(
-    *prefix ? std::string(prefix) : STR_EMPTY,
-//    ns == NS_CLIENT ? NS_CLIENT :
-//    ns == NS_ROSTER ? NS_ROSTER :
-//    ns == NS_GR ? NS_GR :
-    std::string(ns));
+  xmlnsstack_.AddXmlns(*prefix ? prefix : STR_EMPTY, ns);
 }
 
 void
@@ -225,28 +221,25 @@
 }
 
 QName
-XmlParser::ParseContext::ResolveQName(const char *qname, bool isAttr) {
+XmlParser::ParseContext::ResolveQName(const char* qname, bool isAttr) {
   const char *c;
   for (c = qname; *c; ++c) {
     if (*c == ':') {
-      const std::string * result;
-      result = xmlnsstack_.NsForPrefix(std::string(qname, c - qname));
-      if (result == NULL)
-        return QN_EMPTY;
-      const char * localname = c + 1;
-      return QName(*result, localname);
+      const std::pair<std::string, bool> result =
+          xmlnsstack_.NsForPrefix(std::string(qname, c - qname));
+      if (!result.second)
+        return QName();
+      return QName(result.first, c + 1);
     }
   }
-  if (isAttr) {
+  if (isAttr)
     return QName(STR_EMPTY, qname);
-  }
 
-  const std::string * result;
-  result = xmlnsstack_.NsForPrefix(STR_EMPTY);
-  if (result == NULL)
-    return QN_EMPTY;
+  std::pair<std::string, bool> result = xmlnsstack_.NsForPrefix(STR_EMPTY);
+  if (!result.second)
+    return QName();
 
-  return QName(*result, qname);
+  return QName(result.first, qname);
 }
 
 void
@@ -283,4 +276,4 @@
 XmlParser::ParseContext::~ParseContext() {
 }
 
-}
+}  // namespace buzz
diff --git a/talk/xmllite/xmlparser.h b/talk/xmllite/xmlparser.h
index b461e7e..69cde75 100644
--- a/talk/xmllite/xmlparser.h
+++ b/talk/xmllite/xmlparser.h
@@ -25,8 +25,8 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef _xmlparser_h_
-#define _xmlparser_h_
+#ifndef TALK_XMLLITE_XMLPARSER_H_
+#define TALK_XMLLITE_XMLPARSER_H_
 
 #include <string>
 
@@ -116,6 +116,6 @@
   bool sentError_;
 };
 
-}
+}  // namespace buzz
 
-#endif
+#endif  // TALK_XMLLITE_XMLPARSER_H_
diff --git a/talk/xmllite/xmlparser_unittest.cc b/talk/xmllite/xmlparser_unittest.cc
new file mode 100644
index 0000000..24947fb
--- /dev/null
+++ b/talk/xmllite/xmlparser_unittest.cc
@@ -0,0 +1,302 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlparser.h"
+
+using buzz::QName;
+using buzz::XmlParser;
+using buzz::XmlParseContext;
+using buzz::XmlParseHandler;
+
+class XmlParserTestHandler : public XmlParseHandler {
+ public:
+  virtual void StartElement(XmlParseContext * pctx,
+                            const char * name, const char ** atts) {
+    ss_ << "START (" << pctx->ResolveQName(name, false).Merged();
+    while (*atts) {
+      ss_ << ", " << pctx->ResolveQName(*atts, true).Merged()
+          << "='" << *(atts+1) << "'";
+      atts += 2;
+    }
+    ss_ << ") ";
+  }
+  virtual void EndElement(XmlParseContext * pctx, const char * name) {
+    UNUSED(pctx);
+    UNUSED(name);
+    ss_ << "END ";
+  }
+  virtual void CharacterData(XmlParseContext * pctx,
+                             const char * text, int len) {
+    UNUSED(pctx);
+    ss_ << "TEXT (" << std::string(text, len) << ") ";
+  }
+  virtual void Error(XmlParseContext * pctx, XML_Error code) {
+    UNUSED(pctx);
+    ss_ << "ERROR (" << static_cast<int>(code) << ") ";
+  }
+  virtual ~XmlParserTestHandler() {
+  }
+
+  std::string Str() {
+    return ss_.str();
+  }
+
+  std::string StrClear() {
+    std::string result = ss_.str();
+    ss_.str("");
+    return result;
+  }
+
+ private:
+  std::stringstream ss_;
+};
+
+
+TEST(XmlParserTest, TestTrivial) {
+  XmlParserTestHandler handler;
+  XmlParser::ParseXml(&handler, "<testing/>");
+  EXPECT_EQ("START (testing) END ", handler.Str());
+}
+
+TEST(XmlParserTest, TestAttributes) {
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "<testing a='b'/>");
+    EXPECT_EQ("START (testing, a='b') END ", handler.Str());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "<testing e='' long='some text'/>");
+    EXPECT_EQ("START (testing, e='', long='some text') END ", handler.Str());
+  }
+}
+
+TEST(XmlParserTest, TestNesting) {
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler,
+        "<top><first/><second><third></third></second></top>");
+    EXPECT_EQ("START (top) START (first) END START (second) START (third) "
+        "END END END ", handler.Str());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "<top><fifth><deeper><and><deeper/></and>"
+        "<sibling><leaf/></sibling></deeper></fifth><first/><second>"
+        "<third></third></second></top>");
+    EXPECT_EQ("START (top) START (fifth) START (deeper) START (and) START "
+            "(deeper) END END START (sibling) START (leaf) END END END "
+            "END START (first) END START (second) START (third) END END END ",
+            handler.Str());
+  }
+}
+
+TEST(XmlParserTest, TestXmlDecl) {
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "<?xml version=\"1.0\"?><testing/>");
+    EXPECT_EQ("START (testing) END ", handler.Str());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler,
+        "<?xml version=\"1.0\" encoding=\"utf-8\"?><testing/>");
+    EXPECT_EQ("START (testing) END ", handler.Str());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler,
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        "<testing/>");
+    EXPECT_EQ("START (testing) END ", handler.Str());
+  }
+}
+
+TEST(XmlParserTest, TestNamespace) {
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "<top xmlns='my-namespace' a='b'/>");
+    EXPECT_EQ("START (my-namespace:top, xmlns='my-namespace', a='b') END ",
+        handler.Str());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "<foo:top xmlns:foo='my-namespace' "
+          "a='b' foo:c='d'/>");
+    EXPECT_EQ("START (my-namespace:top, "
+        "http://www.w3.org/2000/xmlns/:foo='my-namespace', "
+        "a='b', my-namespace:c='d') END ", handler.Str());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "<top><nested xmlns='my-namespace'><leaf/>"
+        "</nested><sibling/></top>");
+    EXPECT_EQ("START (top) START (my-namespace:nested, xmlns='my-namespace') "
+        "START (my-namespace:leaf) END END START (sibling) END END ",
+        handler.Str());
+  }
+}
+
+TEST(XmlParserTest, TestIncremental) {
+  XmlParserTestHandler handler;
+  XmlParser parser(&handler);
+  std::string fragment;
+
+  fragment = "<stream:stream";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("", handler.StrClear());
+
+  fragment = " id=\"abcdefg\" xmlns=\"";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("", handler.StrClear());
+
+  fragment = "j:c\" xmlns:stream='hm";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("", handler.StrClear());
+
+  fragment = "ph'><test";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("START (hmph:stream, id='abcdefg', xmlns='j:c', "
+      "http://www.w3.org/2000/xmlns/:stream='hmph') ", handler.StrClear());
+
+  fragment = "ing/><again/>abracad";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("START (j:c:testing) END START (j:c:again) END TEXT (abracad) ",
+      handler.StrClear());
+
+  fragment = "abra</stream:";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("TEXT (abra) ", handler.StrClear());
+
+  fragment = "stream>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("END ", handler.StrClear());
+}
+
+TEST(XmlParserTest, TestReset) {
+  {
+    XmlParserTestHandler handler;
+    XmlParser parser(&handler);
+    std::string fragment;
+
+    fragment = "<top><first/><second><third></third>";
+    parser.Parse(fragment.c_str(), fragment.length(), false);
+    EXPECT_EQ("START (top) START (first) END START (second) START (third) END ",
+        handler.StrClear());
+
+    parser.Reset();
+    fragment = "<tip><first/><second><third></third>";
+    parser.Parse(fragment.c_str(), fragment.length(), false);
+    EXPECT_EQ("START (tip) START (first) END START (second) START (third) END ",
+        handler.StrClear());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser parser(&handler);
+    std::string fragment;
+
+    fragment = "<top xmlns='m'>";
+    parser.Parse(fragment.c_str(), fragment.length(), false);
+    EXPECT_EQ("START (m:top, xmlns='m') ", handler.StrClear());
+
+    fragment = "<testing/><frag";
+    parser.Parse(fragment.c_str(), fragment.length(), false);
+    EXPECT_EQ("START (m:testing) END ", handler.StrClear());
+
+    parser.Reset();
+    fragment = "<testing><fragment/";
+    parser.Parse(fragment.c_str(), fragment.length(), false);
+    EXPECT_EQ("START (testing) ", handler.StrClear());
+
+    fragment = ">";
+    parser.Parse(fragment.c_str(), fragment.length(), false);
+    EXPECT_EQ("START (fragment) END ", handler.StrClear());
+  }
+}
+
+TEST(XmlParserTest, TestError) {
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "junk");
+    EXPECT_EQ("ERROR (2) ", handler.Str());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "<top/> garbage ");
+    EXPECT_EQ("START (top) END ERROR (9) ", handler.Str());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "<-hm->");
+    EXPECT_EQ("ERROR (4) ", handler.Str());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler, "<hello>&foobar;</hello>");
+    EXPECT_EQ("START (hello) ERROR (11) ", handler.Str());
+  }
+  {
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler,
+        "<!DOCTYPE HTML PUBLIC \"foobar\" \"barfoo\">");
+    EXPECT_EQ("ERROR (3) ", handler.Str());
+  }
+  {
+    // XmlParser requires utf-8
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler,
+        "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?><test/>");
+    EXPECT_EQ("ERROR (19) ", handler.Str());
+  }
+  {
+    // XmlParser requires version 1.0
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler,
+        "<?xml version=\"2.0\"?><test/>");
+    EXPECT_EQ("ERROR (2) ", handler.Str());
+  }
+  {
+    // XmlParser requires standalone documents
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler,
+        "<?xml version=\"1.0\" standalone=\"no\"?><test/>");
+    EXPECT_EQ("ERROR (2) ", handler.Str());
+  }
+  {
+    // XmlParser doesn't like empty namespace URIs
+    XmlParserTestHandler handler;
+    XmlParser::ParseXml(&handler,
+        "<test xmlns:foo='' foo:bar='huh?'>");
+    EXPECT_EQ("ERROR (2) ", handler.Str());
+  }
+}
diff --git a/talk/xmllite/xmlprinter.cc b/talk/xmllite/xmlprinter.cc
index db6704b..1350454 100644
--- a/talk/xmllite/xmlprinter.cc
+++ b/talk/xmllite/xmlprinter.cc
@@ -25,130 +25,125 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <ostream>
+#include "talk/xmllite/xmlprinter.h"
+
 #include <sstream>
 #include <string>
 #include <vector>
-#include "talk/xmllite/xmlelement.h"
-#include "talk/xmllite/xmlprinter.h"
-#include "talk/xmllite/xmlnsstack.h"
+
 #include "talk/xmllite/xmlconstants.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlnsstack.h"
 
 namespace buzz {
 
 class XmlPrinterImpl {
 public:
-  XmlPrinterImpl(std::ostream * pout,
-    const std::string * const xmlns, int xmlnsCount);
-  void PrintElement(const XmlElement * element);
-  void PrintQuotedValue(const std::string & text);
-  void PrintBodyText(const std::string & text);
-  void PrintCDATAText(const std::string & text);
+  XmlPrinterImpl(std::ostream* pout, XmlnsStack* ns_stack);
+  void PrintElement(const XmlElement* element);
+  void PrintQuotedValue(const std::string& text);
+  void PrintBodyText(const std::string& text);
+  void PrintCDATAText(const std::string& text);
 
 private:
   std::ostream *pout_;
-  XmlnsStack xmlnsStack_;
+  XmlnsStack* ns_stack_;
 };
 
-void
-XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element) {
-  PrintXml(pout, element, NULL, 0);
+void XmlPrinter::PrintXml(std::ostream* pout, const XmlElement* element) {
+  XmlnsStack ns_stack;
+  PrintXml(pout, element, &ns_stack);
 }
 
-void
-XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element,
-    const std::string * const xmlns, int xmlnsCount) {
-  XmlPrinterImpl printer(pout, xmlns, xmlnsCount);
+void XmlPrinter::PrintXml(std::ostream* pout, const XmlElement* element,
+                          XmlnsStack* ns_stack) {
+  XmlPrinterImpl printer(pout, ns_stack);
   printer.PrintElement(element);
 }
 
-XmlPrinterImpl::XmlPrinterImpl(std::ostream * pout,
-    const std::string * const xmlns, int xmlnsCount) :
-  pout_(pout),
-  xmlnsStack_() {
-  int i;
-  for (i = 0; i < xmlnsCount; i += 2) {
-    xmlnsStack_.AddXmlns(xmlns[i], xmlns[i + 1]);
-  }
+XmlPrinterImpl::XmlPrinterImpl(std::ostream* pout, XmlnsStack* ns_stack)
+    : pout_(pout),
+      ns_stack_(ns_stack) {
 }
 
-void
-XmlPrinterImpl::PrintElement(const XmlElement * element) {
-  xmlnsStack_.PushFrame();
+void XmlPrinterImpl::PrintElement(const XmlElement* element) {
+  ns_stack_->PushFrame();
 
   // first go through attrs of pel to add xmlns definitions
-  const XmlAttr * pattr;
-  for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
-    if (pattr->Name() == QN_XMLNS)
-      xmlnsStack_.AddXmlns(STR_EMPTY, pattr->Value());
-    else if (pattr->Name().Namespace() == NS_XMLNS)
-      xmlnsStack_.AddXmlns(pattr->Name().LocalPart(),
-        pattr->Value());
+  const XmlAttr* attr;
+  for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) {
+    if (attr->Name() == QN_XMLNS) {
+      ns_stack_->AddXmlns(STR_EMPTY, attr->Value());
+    } else if (attr->Name().Namespace() == NS_XMLNS) {
+      ns_stack_->AddXmlns(attr->Name().LocalPart(),
+                          attr->Value());
+    }
   }
 
   // then go through qnames to make sure needed xmlns definitons are added
-  std::vector<std::string> newXmlns;
+  std::vector<std::string> new_ns;
   std::pair<std::string, bool> prefix;
-  prefix = xmlnsStack_.AddNewPrefix(element->Name().Namespace(), false);
+  prefix = ns_stack_->AddNewPrefix(element->Name().Namespace(), false);
   if (prefix.second) {
-    newXmlns.push_back(prefix.first);
-    newXmlns.push_back(element->Name().Namespace());
+    new_ns.push_back(prefix.first);
+    new_ns.push_back(element->Name().Namespace());
   }
 
-  for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
-    prefix = xmlnsStack_.AddNewPrefix(pattr->Name().Namespace(), true);
+  for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) {
+    prefix = ns_stack_->AddNewPrefix(attr->Name().Namespace(), true);
     if (prefix.second) {
-      newXmlns.push_back(prefix.first);
-      newXmlns.push_back(pattr->Name().Namespace());
+      new_ns.push_back(prefix.first);
+      new_ns.push_back(attr->Name().Namespace());
     }
   }
 
   // print the element name
-  *pout_ << '<' << xmlnsStack_.FormatQName(element->Name(), false);
+  *pout_ << '<' << ns_stack_->FormatQName(element->Name(), false);
 
   // and the attributes
-  for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
-    *pout_ << ' ' << xmlnsStack_.FormatQName(pattr->Name(), true) << "=\"";
-    PrintQuotedValue(pattr->Value());
+  for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) {
+    *pout_ << ' ' << ns_stack_->FormatQName(attr->Name(), true) << "=\"";
+    PrintQuotedValue(attr->Value());
     *pout_ << '"';
   }
 
   // and the extra xmlns declarations
-  std::vector<std::string>::iterator i(newXmlns.begin());
-  while (i < newXmlns.end()) {
-    if (*i == STR_EMPTY)
+  std::vector<std::string>::iterator i(new_ns.begin());
+  while (i < new_ns.end()) {
+    if (*i == STR_EMPTY) {
       *pout_ << " xmlns=\"" << *(i + 1) << '"';
-    else
+    } else {
       *pout_ << " xmlns:" << *i << "=\"" << *(i + 1) << '"';
+    }
     i += 2;
   }
 
   // now the children
-  const XmlChild * pchild = element->FirstChild();
+  const XmlChild* child = element->FirstChild();
 
-  if (pchild == NULL)
+  if (child == NULL)
     *pout_ << "/>";
   else {
     *pout_ << '>';
-    while (pchild) {
-      if (pchild->IsText()) {
+    while (child) {
+      if (child->IsText()) {
         if (element->IsCDATA()) {
-          PrintCDATAText(pchild->AsText()->Text());
+          PrintCDATAText(child->AsText()->Text());
         } else {
-          PrintBodyText(pchild->AsText()->Text());
+          PrintBodyText(child->AsText()->Text());
         }
-      } else
-        PrintElement(pchild->AsElement());
-      pchild = pchild->NextChild();
+      } else {
+        PrintElement(child->AsElement());
+      }
+      child = child->NextChild();
     }
-    *pout_ << "</" << xmlnsStack_.FormatQName(element->Name(), false) << '>';
+    *pout_ << "</" << ns_stack_->FormatQName(element->Name(), false) << '>';
   }
 
-  xmlnsStack_.PopFrame();
+  ns_stack_->PopFrame();
 }
 
-void
-XmlPrinterImpl::PrintQuotedValue(const std::string & text) {
+void XmlPrinterImpl::PrintQuotedValue(const std::string& text) {
   size_t safe = 0;
   for (;;) {
     size_t unsafe = text.find_first_of("<>&\"", safe);
@@ -169,8 +164,7 @@
   }
 }
 
-void
-XmlPrinterImpl::PrintBodyText(const std::string & text) {
+void XmlPrinterImpl::PrintBodyText(const std::string& text) {
   size_t safe = 0;
   for (;;) {
     size_t unsafe = text.find_first_of("<>&", safe);
@@ -190,9 +184,8 @@
   }
 }
 
-void
-XmlPrinterImpl::PrintCDATAText(const std::string & text) {
+void XmlPrinterImpl::PrintCDATAText(const std::string& text) {
   *pout_ << "<![CDATA[" << text << "]]>";
 }
 
-}
+}  // namespace buzz
diff --git a/talk/xmllite/xmlprinter.h b/talk/xmllite/xmlprinter.h
index 96900d0..90cc255 100644
--- a/talk/xmllite/xmlprinter.h
+++ b/talk/xmllite/xmlprinter.h
@@ -2,48 +2,48 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef _xmlprinter_h_
-#define _xmlprinter_h_
+#ifndef TALK_XMLLITE_XMLPRINTER_H_
+#define TALK_XMLLITE_XMLPRINTER_H_
 
 #include <iosfwd>
 #include <string>
-#include "talk/base/scoped_ptr.h"
 
 namespace buzz {
 
 class XmlElement;
+class XmlnsStack;
 
 class XmlPrinter {
-public:
-  static void PrintXml(std::ostream * pout, const XmlElement * pelt);
+ public:
+  static void PrintXml(std::ostream* pout, const XmlElement* pelt);
 
-  static void PrintXml(std::ostream * pout, const XmlElement * pelt,
-    const std::string * const xmlns, int xmlnsCount);
+  static void PrintXml(std::ostream* pout, const XmlElement* pelt,
+                       XmlnsStack* ns_stack);
 };
 
-}
+}  // namespace buzz
 
-#endif
+#endif  // TALK_XMLLITE_XMLPRINTER_H_
diff --git a/talk/xmllite/xmlprinter_unittest.cc b/talk/xmllite/xmlprinter_unittest.cc
new file mode 100644
index 0000000..60b0e42
--- /dev/null
+++ b/talk/xmllite/xmlprinter_unittest.cc
@@ -0,0 +1,62 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmllite/xmlprinter.h"
+
+#include <sstream>
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlnsstack.h"
+
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmlnsStack;
+using buzz::XmlPrinter;
+
+TEST(XmlPrinterTest, TestBasicPrinting) {
+  XmlElement elt(QName("google:test", "first"));
+  std::stringstream ss;
+  XmlPrinter::PrintXml(&ss, &elt);
+  EXPECT_EQ("<test:first xmlns:test=\"google:test\"/>", ss.str());
+}
+
+TEST(XmlPrinterTest, TestNamespacedPrinting) {
+  XmlElement elt(QName("google:test", "first"));
+  elt.AddElement(new XmlElement(QName("nested:test", "second")));
+  std::stringstream ss;
+
+  XmlnsStack ns_stack;
+  ns_stack.AddXmlns("gg", "google:test");
+  ns_stack.AddXmlns("", "nested:test");
+
+  XmlPrinter::PrintXml(&ss, &elt, &ns_stack);
+  EXPECT_EQ("<gg:first><second/></gg:first>", ss.str());
+}
diff --git a/talk/xmpp/asyncsocket.h b/talk/xmpp/asyncsocket.h
index e4bce7f..fb4ef02 100644
--- a/talk/xmpp/asyncsocket.h
+++ b/talk/xmpp/asyncsocket.h
@@ -69,8 +69,9 @@
   virtual bool Write(const char * data, size_t len) = 0;
   virtual bool Close() = 0;
 #if defined(FEATURE_ENABLE_SSL)
-  // We allow matching any passed domain.
-  // If both names are passed as empty, we do not require a match.
+  // We allow matching any passed domain.  This allows us to avoid
+  // handling the valuable certificates for logins into proxies.  If
+  // both names are passed as empty, we do not require a match.
   virtual bool StartTls(const std::string & domainname) = 0;
 #endif
 
diff --git a/talk/xmpp/chatroommodule.h b/talk/xmpp/chatroommodule.h
new file mode 100644
index 0000000..fddbecd
--- /dev/null
+++ b/talk/xmpp/chatroommodule.h
@@ -0,0 +1,258 @@
+/*
+ * 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 _multiuserchatmodule_h_
+#define _multiuserchatmodule_h_
+
+#include "talk/xmpp/module.h"
+#include "talk/xmpp/rostermodule.h"
+
+namespace buzz {
+
+// forward declarations
+class XmppChatroomModule;
+class XmppChatroomHandler;
+class XmppChatroomMember;
+class XmppChatroomMemberEnumerator;
+
+enum XmppChatroomState {
+  XMPP_CHATROOM_STATE_NOT_IN_ROOM      = 0,
+  XMPP_CHATROOM_STATE_REQUESTED_ENTER  = 1,
+  XMPP_CHATROOM_STATE_IN_ROOM          = 2,
+  XMPP_CHATROOM_STATE_REQUESTED_EXIT   = 3,
+};
+
+//! Module that encapsulates a chatroom. 
+class XmppChatroomModule : public XmppModule {
+public:
+
+  //! Creates a new XmppChatroomModule
+  static XmppChatroomModule* Create();
+  virtual ~XmppChatroomModule() {}
+
+  //! Sets the chatroom handler (callbacks) for the chatroom
+  virtual XmppReturnStatus set_chatroom_handler(XmppChatroomHandler* handler) = 0;
+
+  //! Gets the chatroom handler for the module
+  virtual XmppChatroomHandler* chatroom_handler() = 0;
+
+  //! Sets the jid of the chatroom.
+  //! Has to be set before entering the chatroom and can't be changed
+  //! while in the chatroom
+  virtual XmppReturnStatus set_chatroom_jid(const Jid& chatroom_jid) = 0;
+
+  //! The jid for the chatroom
+  virtual const Jid& chatroom_jid() const = 0;
+
+  //! Sets the nickname of the member
+  //! Has to be set before entering the chatroom and can't be changed
+  //! while in the chatroom
+  virtual XmppReturnStatus set_nickname(const std::string& nickname) = 0;
+
+  //! The nickname of the member in the chatroom
+  virtual const std::string& nickname() const = 0;
+
+  //! Returns the jid of the member (this is the chatroom_jid plus the
+  //! nickname as the resource name)
+  virtual const Jid member_jid() const = 0;
+
+  //! Requests that the user enter a chatroom
+  //! The EnterChatroom callback will be called when the request is complete.
+  //! Password should be empty for a room that doesn't require a password
+  //! If the room doesn't exist, the server will create an "Instant Room" if the 
+  //! server policy supports this action.
+  //! There will be different methods for creating/configuring a "Reserved Room"
+  //! Async callback for this method is ChatroomEnteredStatus
+  virtual XmppReturnStatus RequestEnterChatroom(const std::string& password) = 0;
+
+  //! Requests that the user exit a chatroom
+  //! Async callback for this method is ChatroomExitedStatus
+  virtual XmppReturnStatus RequestExitChatroom() = 0;
+
+  //! Requests a status change
+  //! status is the standard XMPP status code
+  //! extended_status is the extended status when status is XMPP_PRESENCE_XA
+  virtual XmppReturnStatus RequestStatusChange(XmppPresenceShow status, 
+                                               const std::string& extended_status) = 0;
+
+  //! Returns the number of members in the room
+  virtual size_t GetChatroomMemberCount() = 0;
+
+  //! Gets an enumerator for the members in the chatroom
+  //! The caller must delete the enumerator when the caller is finished with it. 
+  //! The caller must also ensure that the lifetime of the enumerator is 
+  //! scoped by the XmppChatRoomModule that created it.
+  virtual XmppReturnStatus CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator) = 0;
+
+  //! Gets the subject of the chatroom
+  virtual const std::string& subject() = 0;
+
+  //! Returns the current state of the user with respect to the chatroom
+  virtual XmppChatroomState state() = 0;
+
+  virtual XmppReturnStatus SendMessage(const XmlElement& message) = 0;
+};
+
+//! Class for enumerating participatns
+class XmppChatroomMemberEnumerator {
+public:
+  virtual ~XmppChatroomMemberEnumerator() { }
+  //! Returns the member at the current position
+  //! Returns null if the enumerator is before the beginning
+  //! or after the end of the collection
+  virtual XmppChatroomMember* current() = 0;
+
+  //! Returns whether the enumerator is valid
+  //! This returns true if the collection has changed
+  //! since the enumerator was created
+  virtual bool IsValid() = 0;
+
+  //! Returns whether the enumerator is before the beginning
+  //! This is the initial state of the enumerator
+  virtual bool IsBeforeBeginning() = 0;
+
+  //! Returns whether the enumerator is after the end
+  virtual bool IsAfterEnd() = 0;
+
+  //! Advances the enumerator to the next position
+  //! Returns false is the enumerator is advanced
+  //! off the end of the collection
+  virtual bool Next() = 0;
+
+  //! Advances the enumerator to the previous position
+  //! Returns false is the enumerator is advanced
+  //! off the end of the collection
+  virtual bool Prev() = 0;
+};
+
+
+//! Represents a single member in a chatroom
+class XmppChatroomMember {
+public:
+  virtual ~XmppChatroomMember() { }
+    
+  //! The jid for the member in the chatroom
+  virtual const Jid member_jid() const = 0;
+
+  //! The full jid for the member 
+  //! This is only available in non-anonymous rooms.
+  //! If the room is anonymous, this returns JID_EMPTY
+  virtual const Jid full_jid() const = 0;
+
+   //! Returns the backing presence for this member
+  virtual const XmppPresence* presence() const = 0;
+
+  //! The nickname for this member
+  virtual const std::string name() const = 0;
+};
+
+//! Status codes for ChatroomEnteredStatus callback
+enum XmppChatroomEnteredStatus
+{
+  //! User successfully entered the room
+  XMPP_CHATROOM_ENTERED_SUCCESS                    = 0,
+  //! The nickname confliced with somebody already in the room
+  XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT  = 1,
+  //! A password is required to enter the room
+  XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED  = 2,
+  //! The specified password was incorrect
+  XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_INCORRECT = 3,
+  //! The user is not a member of a member-only room
+  XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER       = 4,
+  //! The user cannot enter because the user has been banned
+  XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED      = 5,
+  //! The room has the maximum number of users already
+  XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS          = 6,
+  //! The room has been locked by an administrator
+  XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED        = 7,
+  //! Some other reason
+  XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED        = 2000,
+};
+
+//! Status codes for ChatroomExitedStatus callback
+enum XmppChatroomExitedStatus
+{
+  //! The user requested to exit and did so
+  XMPP_CHATROOM_EXITED_REQUESTED                   = 0,
+  //! The user was banned from the room
+  XMPP_CHATROOM_EXITED_BANNED                      = 1,
+  //! The user has been kicked out of the room
+  XMPP_CHATROOM_EXITED_KICKED                      = 2,
+  //! The user has been removed from the room because the
+  //! user is no longer a member of a member-only room
+  //! or the room has changed to membership-only
+  XMPP_CHATROOM_EXITED_NOT_A_MEMBER                = 3,
+  //! The system is shutting down
+  XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN             = 4,
+  //! For some other reason
+  XMPP_CHATROOM_EXITED_UNSPECIFIED                 = 5,             
+};
+
+//! The XmppChatroomHandler is the interface for callbacks from the
+//! the chatroom
+class XmppChatroomHandler {
+public:
+  //! Indicates the response to RequestEnterChatroom method
+  //! XMPP_CHATROOM_SUCCESS represents success.
+  //! Other status codes are for errors
+  virtual void ChatroomEnteredStatus(XmppChatroomModule* room,
+                                     XmppChatroomEnteredStatus status) = 0;
+
+
+  //! Indicates that the user has exited the chatroom, either due to 
+  //! a call to RequestExitChatroom or for some other reason.
+  //! status indicates the reason the user exited
+  virtual void ChatroomExitedStatus(XmppChatroomModule* room,
+                                    XmppChatroomExitedStatus status) = 0;
+
+  //! Indicates a member entered the room.
+  virtual void MemberEntered(XmppChatroomModule* room,
+                                  const XmppChatroomMember* entered_member) = 0;
+
+  //! Indicates that a member exited the room.
+  virtual void MemberExited(XmppChatroomModule* room,
+                              const XmppChatroomMember* exited_member) = 0;
+
+  //! Indicates that the data for the member has changed
+  //! (such as the nickname or presence)
+  virtual void MemberChanged(XmppChatroomModule* room,
+                              size_t index) = 0;
+
+  //! Indicates a new message has been received
+  //! message is the message - 
+  // $TODO - message should be changed
+  //! to a strongly-typed message class that contains info
+  //! such as the sender, message bodies, etc.,
+  virtual void MessageReceived(XmppChatroomModule* room,
+                               const XmlElement& message) = 0;
+};
+
+
+}
+
+#endif
+
diff --git a/talk/xmpp/chatroommodule_unittest.cc b/talk/xmpp/chatroommodule_unittest.cc
new file mode 100644
index 0000000..b857551
--- /dev/null
+++ b/talk/xmpp/chatroommodule_unittest.cc
@@ -0,0 +1,295 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "common/common.h"
+#include "buzz/xmppengine.h"
+#include "buzz/xmlelement.h"
+#include "buzz/chatroommodule.h"
+#include "buzz/constants.h"
+#include "engine/util_unittest.h"
+#include "test/unittest.h"
+#include "test/unittest-inl.h"
+
+#define TEST_OK(x) TEST_EQ((x),XMPP_RETURN_OK)
+#define TEST_BADARGUMENT(x) TEST_EQ((x),XMPP_RETURN_BADARGUMENT)
+
+namespace buzz {
+
+class MultiUserChatModuleTest;
+
+static void
+WriteEnteredStatus(std::ostream& os, XmppChatroomEnteredStatus status) {
+  switch(status) {
+    case XMPP_CHATROOM_ENTERED_SUCCESS:
+      os<<"success";
+      break;
+    case XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT:
+      os<<"failure(nickname conflict)";
+      break;
+    case XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED:
+      os<<"failure(password required)";
+      break;
+    case XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_INCORRECT:
+      os<<"failure(password incorrect)";
+      break;
+    case XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER:
+      os<<"failure(not a member)";
+      break;
+    case XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED:
+      os<<"failure(member banned)";
+      break;
+    case XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS:
+      os<<"failure(max users)";
+      break;
+    case XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED:
+      os<<"failure(room locked)";
+      break;
+    case XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED:
+      os<<"failure(unspecified)";
+      break;
+    default:
+      os<<"unknown";
+      break;
+  } 
+}
+
+static void
+WriteExitedStatus(std::ostream& os, XmppChatroomExitedStatus status) {
+  switch (status) {
+    case XMPP_CHATROOM_EXITED_REQUESTED:
+      os<<"requested";
+      break;
+    case XMPP_CHATROOM_EXITED_BANNED:
+      os<<"banned";
+      break;
+    case XMPP_CHATROOM_EXITED_KICKED:
+      os<<"kicked";
+      break;
+    case XMPP_CHATROOM_EXITED_NOT_A_MEMBER:
+      os<<"not member";
+      break;
+    case XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN:
+      os<<"system shutdown";
+      break;
+    case XMPP_CHATROOM_EXITED_UNSPECIFIED:
+      os<<"unspecified";
+      break;
+    default:
+      os<<"unknown";
+      break;
+  }
+}
+
+//! This session handler saves all calls to a string.  These are events and
+//! data delivered form the engine to application code.
+class XmppTestChatroomHandler : public XmppChatroomHandler {
+public:
+  XmppTestChatroomHandler() {}
+  virtual ~XmppTestChatroomHandler() {}
+
+  void ChatroomEnteredStatus(XmppChatroomModule* room,
+                             XmppChatroomEnteredStatus status) {
+    UNUSED(room);
+    ss_ <<"[ChatroomEnteredStatus status: ";
+    WriteEnteredStatus(ss_, status);
+    ss_ <<"]";
+  }
+
+
+  void ChatroomExitedStatus(XmppChatroomModule* room,
+                            XmppChatroomExitedStatus status) {
+    UNUSED(room);
+    ss_ <<"[ChatroomExitedStatus status: ";
+    WriteExitedStatus(ss_, status);
+    ss_ <<"]";
+  }
+
+  void MemberEntered(XmppChatroomModule* room, 
+                          const XmppChatroomMember* entered_member) {
+    UNUSED(room);
+    ss_ << "[MemberEntered " << entered_member->member_jid().Str() << "]";
+  }
+
+  void MemberExited(XmppChatroomModule* room,
+                         const XmppChatroomMember* exited_member) {
+    UNUSED(room);
+    ss_ << "[MemberExited " << exited_member->member_jid().Str() << "]";
+  }
+
+  void MemberChanged(XmppChatroomModule* room, size_t index) {
+    UNUSED2(room, index);
+  }
+
+  virtual void MessageReceived(XmppChatroomModule* room, const XmlElement& message) {
+    UNUSED2(room, message);
+  }
+
+ 
+  std::string Str() {
+    return ss_.str();
+  }
+
+  std::string StrClear() {
+    std::string result = ss_.str();
+    ss_.str("");
+    return result;
+  }
+
+private:
+  std::stringstream ss_;
+};
+
+//! This is the class that holds all of the unit test code for the
+//! roster module
+class XmppChatroomModuleTest : public UnitTest {
+public:
+  XmppChatroomModuleTest() {}
+
+  void TestEnterExitChatroom() {
+    std::stringstream dump;
+
+    // Configure the engine
+    scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+    XmppTestHandler handler(engine.get());
+
+    // Configure the module and handler
+    scoped_ptr<XmppChatroomModule> chatroom(XmppChatroomModule::Create());
+
+    // Configure the module handler
+    chatroom->RegisterEngine(engine.get());
+
+    // Set up callbacks
+    engine->SetOutputHandler(&handler);
+    engine->AddStanzaHandler(&handler);
+    engine->SetSessionHandler(&handler);
+
+    // Set up minimal login info
+    engine->SetUser(Jid("david@my-server"));
+    engine->SetPassword("david");
+
+    // Do the whole login handshake
+    RunLogin(this, engine.get(), &handler);
+    TEST_EQ("", handler.OutputActivity());
+
+    // Get the chatroom and set the handler
+    XmppTestChatroomHandler chatroom_handler;
+    chatroom->set_chatroom_handler(static_cast<XmppChatroomHandler*>(&chatroom_handler));
+
+    // try to enter the chatroom
+    TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_NOT_IN_ROOM);
+    chatroom->set_nickname("thirdwitch");
+    chatroom->set_chatroom_jid(Jid("darkcave@my-server"));
+    chatroom->RequestEnterChatroom("");
+    TEST_EQ(chatroom_handler.StrClear(), "");
+    TEST_EQ(handler.OutputActivity(),
+      "<presence to=\"darkcave@my-server/thirdwitch\">"
+        "<muc:x xmlns:muc=\"http://jabber.org/protocol/muc\"/>"
+      "</presence>");
+    TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_ENTER);
+
+    // simulate the server and test the client
+    std::string input;
+    input = "<presence from=\"darkcave@my-server/firstwitch\" to=\"david@my-server\">"
+             "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+              "<item affiliation=\"owner\" role=\"participant\"/>"
+             "</x>"
+            "</presence>";
+    TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+    TEST_EQ(chatroom_handler.StrClear(), "");
+    TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_ENTER);
+
+    input = "<presence from=\"darkcave@my-server/secondwitch\" to=\"david@my-server\">"
+             "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+              "<item affiliation=\"member\" role=\"participant\"/>"
+             "</x>"
+            "</presence>";
+    TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+    TEST_EQ(chatroom_handler.StrClear(), "");
+    TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_ENTER);
+
+    input = "<presence from=\"darkcave@my-server/thirdwitch\" to=\"david@my-server\">"
+             "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+              "<item affiliation=\"member\" role=\"participant\"/>"
+             "</x>"
+            "</presence>";
+    TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+    TEST_EQ(chatroom_handler.StrClear(),
+      "[ChatroomEnteredStatus status: success]");
+    TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_IN_ROOM);
+
+    // simulate somebody else entering the room after we entered
+    input = "<presence from=\"darkcave@my-server/fourthwitch\" to=\"david@my-server\">"
+             "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+              "<item affiliation=\"member\" role=\"participant\"/>"
+             "</x>"
+            "</presence>";
+    TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+    TEST_EQ(chatroom_handler.StrClear(), "[MemberEntered darkcave@my-server/fourthwitch]");
+    TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_IN_ROOM);
+
+    // simulate somebody else leaving the room after we entered
+    input = "<presence from=\"darkcave@my-server/secondwitch\" to=\"david@my-server\" type=\"unavailable\">"
+             "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+              "<item affiliation=\"member\" role=\"participant\"/>"
+             "</x>"
+            "</presence>";
+    TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+    TEST_EQ(chatroom_handler.StrClear(), "[MemberExited darkcave@my-server/secondwitch]");
+    TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_IN_ROOM);
+
+    // try to leave the room
+    chatroom->RequestExitChatroom();
+    TEST_EQ(chatroom_handler.StrClear(), "");
+    TEST_EQ(handler.OutputActivity(),
+      "<presence to=\"darkcave@my-server/thirdwitch\" type=\"unavailable\"/>");
+    TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_EXIT);
+
+    // simulate the server and test the client
+    input = "<presence from=\"darkcave@my-server/thirdwitch\" to=\"david@my-server\" type=\"unavailable\">"
+             "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+              "<item affiliation=\"member\" role=\"participant\"/>"
+             "</x>"
+            "</presence>";
+    TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+    TEST_EQ(chatroom_handler.StrClear(),
+      "[ChatroomExitedStatus status: requested]");
+    TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_NOT_IN_ROOM);
+  }
+
+};
+
+// A global function that creates the test suite for this set of tests.
+TestBase* ChatroomModuleTest_Create() {
+  TestSuite* suite = new TestSuite("ChatroomModuleTest");
+  ADD_TEST(suite, XmppChatroomModuleTest, TestEnterExitChatroom);
+  return suite;
+}
+
+}
diff --git a/talk/xmpp/chatroommoduleimpl.cc b/talk/xmpp/chatroommoduleimpl.cc
new file mode 100644
index 0000000..0fbbe7e
--- /dev/null
+++ b/talk/xmpp/chatroommoduleimpl.cc
@@ -0,0 +1,676 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <vector>
+#include <string>
+#include <map>
+#include <algorithm>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/moduleimpl.h"
+#include "talk/xmpp/chatroommodule.h"
+
+namespace buzz {
+
+// forward declarations
+class XmppChatroomImpl;
+class XmppChatroomMemberImpl;
+
+//! Module that encapsulates multiple chatrooms.
+//! Each chatroom is represented by an XmppChatroomImpl instance
+class XmppChatroomModuleImpl : public XmppChatroomModule,
+  public XmppModuleImpl, public XmppIqHandler {
+public:
+  IMPLEMENT_XMPPMODULE
+
+   // Creates a chatroom with specified Jid
+  XmppChatroomModuleImpl();
+  ~XmppChatroomModuleImpl();
+
+  // XmppChatroomModule
+  virtual XmppReturnStatus set_chatroom_handler(XmppChatroomHandler* handler);
+  virtual XmppChatroomHandler* chatroom_handler();
+  virtual XmppReturnStatus set_chatroom_jid(const Jid& chatroom_jid);
+  virtual const Jid& chatroom_jid() const;
+  virtual XmppReturnStatus set_nickname(const std::string& nickname);
+  virtual const std::string& nickname() const;
+  virtual const Jid member_jid() const;
+  virtual XmppReturnStatus RequestEnterChatroom(const std::string& password);
+  virtual XmppReturnStatus RequestExitChatroom();
+  virtual XmppReturnStatus RequestStatusChange(XmppPresenceShow status, 
+                                       const std::string& extended_status);
+  virtual size_t GetChatroomMemberCount();
+  virtual XmppReturnStatus CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator);
+  virtual const std::string& subject();
+  virtual XmppChatroomState state() { return chatroom_state_; }
+  virtual XmppReturnStatus SendMessage(const XmlElement& message);
+
+  // XmppModule
+  virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) {UNUSED2(cookie, pelStanza);}
+  virtual bool HandleStanza(const XmlElement *);
+
+private:
+  friend class XmppChatroomMemberEnumeratorImpl;
+
+  XmppReturnStatus ServerChangeMyPresence(const XmlElement& presence);
+  XmppReturnStatus ClientChangeMyPresence(XmppChatroomState new_state);
+  XmppReturnStatus ChangePresence(XmppChatroomState new_state, const XmlElement* presence, bool isServer);
+  XmppReturnStatus ServerChangedOtherPresence(const XmlElement& presence_element);
+  XmppChatroomEnteredStatus GetEnterFailureFromXml(const XmlElement* presence);
+  XmppChatroomExitedStatus GetExitFailureFromXml(const XmlElement* presence);
+  
+  bool CheckEnterChatroomStateOk();
+
+  void FireEnteredStatus(XmppChatroomEnteredStatus status);
+  void FireExitStatus(XmppChatroomExitedStatus status);
+  void FireMessageReceived(const XmlElement& message);
+  void FireMemberEntered(const XmppChatroomMember* entered_member);
+  void FireMemberExited(const XmppChatroomMember* exited_member);
+
+
+  typedef std::map<Jid, XmppChatroomMemberImpl*> JidMemberMap;
+  
+  XmppChatroomHandler*              chatroom_handler_;
+  Jid                               chatroom_jid_;
+  std::string                       nickname_;
+  XmppChatroomState                 chatroom_state_;
+  JidMemberMap                      chatroom_jid_members_;
+  int                               chatroom_jid_members_version_;
+};
+
+
+class XmppChatroomMemberImpl : public XmppChatroomMember {
+public:
+  ~XmppChatroomMemberImpl() {}
+  XmppReturnStatus SetPresence(const XmppPresence* presence);
+
+  // XmppChatroomMember
+  const Jid member_jid() const;
+  const Jid full_jid() const;
+  const std::string name() const;
+  const XmppPresence* presence() const;
+
+private:
+  talk_base::scoped_ptr<XmppPresence>  presence_;
+};
+
+class XmppChatroomMemberEnumeratorImpl : 
+        public XmppChatroomMemberEnumerator  {
+public:
+  XmppChatroomMemberEnumeratorImpl(XmppChatroomModuleImpl::JidMemberMap* chatroom_jid_members,
+                                        int* map_version);
+
+  // XmppChatroomMemberEnumerator
+  virtual XmppChatroomMember* current();
+  virtual bool Next();
+  virtual bool Prev();
+  virtual bool IsValid();
+  virtual bool IsBeforeBeginning();
+  virtual bool IsAfterEnd();
+
+private:
+  XmppChatroomModuleImpl::JidMemberMap*           map_;
+  int                                             map_version_created_;
+  int*                                            map_version_;
+  XmppChatroomModuleImpl::JidMemberMap::iterator  iterator_;
+  bool                                            before_beginning_;
+};
+
+
+// XmppChatroomModuleImpl ------------------------------------------------
+XmppChatroomModule *
+XmppChatroomModule::Create() {
+  return new XmppChatroomModuleImpl();
+}
+
+XmppChatroomModuleImpl::XmppChatroomModuleImpl() :
+  chatroom_handler_(NULL), 
+  chatroom_jid_(STR_EMPTY),
+  chatroom_state_(XMPP_CHATROOM_STATE_NOT_IN_ROOM),
+  chatroom_jid_members_version_(0) {
+}
+
+XmppChatroomModuleImpl::~XmppChatroomModuleImpl() {
+  JidMemberMap::iterator iterator = chatroom_jid_members_.begin();
+  while (iterator != chatroom_jid_members_.end()) {
+    delete iterator->second;
+    iterator++;
+  }
+}
+
+
+bool 
+XmppChatroomModuleImpl::HandleStanza(const XmlElement* stanza) {
+  ASSERT(engine() != NULL);
+
+  // we handle stanzas that are for one of our chatrooms
+  Jid from_jid = Jid(stanza->Attr(QN_FROM));
+  // see if it's one of our chatrooms
+  if (chatroom_jid_ != from_jid.BareJid()) {
+    return false; // not one of our chatrooms
+  } else {
+    // handle presence stanza
+    if (stanza->Name() == QN_PRESENCE) {
+      if (from_jid == member_jid()) {
+        ServerChangeMyPresence(*stanza);
+      } else {
+        ServerChangedOtherPresence(*stanza);
+      }
+    } else if (stanza->Name() == QN_MESSAGE) {
+      FireMessageReceived(*stanza);
+    }
+    return true;
+  }
+}
+
+
+XmppReturnStatus
+XmppChatroomModuleImpl::set_chatroom_handler(XmppChatroomHandler* handler) {
+  // Calling with NULL removes the handler.
+  chatroom_handler_ = handler;
+  return XMPP_RETURN_OK;
+}
+
+
+XmppChatroomHandler* 
+XmppChatroomModuleImpl::chatroom_handler() {
+  return chatroom_handler_;
+}
+
+XmppReturnStatus 
+XmppChatroomModuleImpl::set_chatroom_jid(const Jid& chatroom_jid) {
+  if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) {
+    return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call,  diff error code?
+  }
+  if (chatroom_jid != chatroom_jid.BareJid()) {
+    // chatroom_jid must be a bare jid
+    return XMPP_RETURN_BADARGUMENT;
+  }
+  
+  chatroom_jid_ = chatroom_jid;
+  return XMPP_RETURN_OK;
+}
+
+const Jid& 
+XmppChatroomModuleImpl::chatroom_jid() const {
+  return chatroom_jid_;
+}
+
+ XmppReturnStatus 
+ XmppChatroomModuleImpl::set_nickname(const std::string& nickname) {
+  if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) {
+    return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call,  diff error code?
+  }
+  nickname_ = nickname;
+  return XMPP_RETURN_OK;
+ }
+
+ const std::string& 
+ XmppChatroomModuleImpl::nickname() const {
+  return nickname_;
+ }
+
+const Jid
+XmppChatroomModuleImpl::member_jid() const {
+  return Jid(chatroom_jid_.node(), chatroom_jid_.domain(), nickname_);
+}
+
+
+bool 
+XmppChatroomModuleImpl::CheckEnterChatroomStateOk() {
+  if (chatroom_jid_.IsValid() == false) {
+    ASSERT(0);
+    return false;
+  }
+  if (nickname_ == STR_EMPTY) {
+    ASSERT(0);
+    return false;
+  }
+  return true;
+}
+
+XmppReturnStatus 
+XmppChatroomModuleImpl::RequestEnterChatroom(const std::string& password) {
+  UNUSED(password);
+  if (!engine())
+    return XMPP_RETURN_BADSTATE;
+
+  if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM)
+    return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call,  diff error code?
+
+  if (CheckEnterChatroomStateOk() == false) {
+    return XMPP_RETURN_BADSTATE;
+  }
+
+  // entering a chatroom is a presence request to the server
+  XmlElement element(QN_PRESENCE);
+  element.AddAttr(QN_TO, member_jid().Str());
+  element.AddElement(new XmlElement(QN_MUC_X));
+  XmppReturnStatus status = engine()->SendStanza(&element);
+  if (status == XMPP_RETURN_OK) {
+    return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_ENTER);
+  }
+  return status;
+}
+
+
+XmppReturnStatus 
+XmppChatroomModuleImpl::RequestExitChatroom() {
+  if (!engine())
+    return XMPP_RETURN_BADSTATE;
+
+  // currently, can't leave a room unless you've entered
+  // no way to cancel a pending enter call - is that bad?
+  if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM)
+    return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call,  diff error code?
+
+  // exiting a chatroom is a presence request to the server
+  XmlElement element(QN_PRESENCE);
+  element.AddAttr(QN_TO, member_jid().Str());
+  element.AddAttr(QN_TYPE, "unavailable");
+  XmppReturnStatus status = engine()->SendStanza(&element);
+  if (status == XMPP_RETURN_OK) {
+    return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_EXIT);
+  }
+  return status;
+}
+
+XmppReturnStatus 
+XmppChatroomModuleImpl::RequestStatusChange(XmppPresenceShow status, 
+                                     const std::string& extended_status) {
+  UNUSED2(status, extended_status);
+  return XMPP_RETURN_BADSTATE; //NYI
+}
+
+
+
+size_t 
+XmppChatroomModuleImpl::GetChatroomMemberCount() {
+  return chatroom_jid_members_.size();
+}
+
+XmppReturnStatus 
+XmppChatroomModuleImpl::CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator) {
+  *enumerator = new XmppChatroomMemberEnumeratorImpl(&chatroom_jid_members_, &chatroom_jid_members_version_);
+  return XMPP_RETURN_OK;
+}
+
+const std::string& 
+XmppChatroomModuleImpl::subject() {
+  return STR_EMPTY; //NYI
+}
+
+XmppReturnStatus 
+XmppChatroomModuleImpl::SendMessage(const XmlElement& message) {
+  XmppReturnStatus xmpp_status = XMPP_RETURN_OK;
+  
+  // can only send a message if we're in the room
+  if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) {
+    return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call,  diff error code?
+  }
+
+  if (message.Name() != QN_MESSAGE) {
+    IFR(XMPP_RETURN_BADARGUMENT);
+  }
+  
+  const std::string& type = message.Attr(QN_TYPE);
+  if (type != "groupchat") {
+    IFR(XMPP_RETURN_BADARGUMENT);
+  }
+
+  if (message.HasAttr(QN_FROM)) {
+    IFR(XMPP_RETURN_BADARGUMENT);
+  }
+
+  if (message.Attr(QN_TO) != chatroom_jid_.Str()) {
+    IFR(XMPP_RETURN_BADARGUMENT);
+  }
+
+  IFR(engine()->SendStanza(&message));
+
+  return xmpp_status;
+}
+
+enum TransitionType {
+  TRANSITION_TYPE_NONE                 = 0, 
+  TRANSITION_TYPE_ENTER_SUCCESS        = 1,
+  TRANSITION_TYPE_ENTER_FAILURE        = 2,
+  TRANSITION_TYPE_EXIT_VOLUNTARILY     = 3,
+  TRANSITION_TYPE_EXIT_INVOLUNTARILY   = 4,
+};
+
+struct StateTransitionDescription {
+  XmppChatroomState old_state;
+  XmppChatroomState new_state;
+  bool              is_valid_server_transition;
+  bool              is_valid_client_transition;
+  TransitionType    transition_type;
+};
+
+StateTransitionDescription Transitions[] = {
+  { XMPP_CHATROOM_STATE_NOT_IN_ROOM,     XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, true,  TRANSITION_TYPE_NONE, },
+  { XMPP_CHATROOM_STATE_NOT_IN_ROOM,     XMPP_CHATROOM_STATE_IN_ROOM,         false, false, TRANSITION_TYPE_ENTER_SUCCESS, },
+  { XMPP_CHATROOM_STATE_NOT_IN_ROOM,     XMPP_CHATROOM_STATE_REQUESTED_EXIT,  false, false, TRANSITION_TYPE_NONE, },
+  { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_NOT_IN_ROOM,     true,  false, TRANSITION_TYPE_ENTER_FAILURE, },
+  { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_IN_ROOM,         true,  false, TRANSITION_TYPE_ENTER_SUCCESS, },
+  { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_REQUESTED_EXIT,  false, false, TRANSITION_TYPE_NONE, },
+  { XMPP_CHATROOM_STATE_IN_ROOM,         XMPP_CHATROOM_STATE_NOT_IN_ROOM,     true,  false, TRANSITION_TYPE_EXIT_INVOLUNTARILY,  },
+  { XMPP_CHATROOM_STATE_IN_ROOM,         XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, },
+  { XMPP_CHATROOM_STATE_IN_ROOM,         XMPP_CHATROOM_STATE_REQUESTED_EXIT,  false, true,  TRANSITION_TYPE_NONE, },
+  { XMPP_CHATROOM_STATE_REQUESTED_EXIT,  XMPP_CHATROOM_STATE_NOT_IN_ROOM,     true,  false, TRANSITION_TYPE_EXIT_VOLUNTARILY, },
+  { XMPP_CHATROOM_STATE_REQUESTED_EXIT,  XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, },
+  { XMPP_CHATROOM_STATE_REQUESTED_EXIT,  XMPP_CHATROOM_STATE_IN_ROOM,         false, false, TRANSITION_TYPE_NONE, },
+};
+
+
+
+void
+XmppChatroomModuleImpl::FireEnteredStatus(XmppChatroomEnteredStatus status) {
+  if (chatroom_handler_)
+    chatroom_handler_->ChatroomEnteredStatus(this, status);
+}
+
+void
+XmppChatroomModuleImpl::FireExitStatus(XmppChatroomExitedStatus status) {
+  if (chatroom_handler_)
+    chatroom_handler_->ChatroomExitedStatus(this, status);
+}
+
+void 
+XmppChatroomModuleImpl::FireMessageReceived(const XmlElement& message) {
+  if (chatroom_handler_)
+    chatroom_handler_->MessageReceived(this, message);
+}
+
+void 
+XmppChatroomModuleImpl::FireMemberEntered(const XmppChatroomMember* entered_member) {
+  // only fire if we're in the room
+  if (chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) {
+    if (chatroom_handler_)
+      chatroom_handler_->MemberEntered(this, entered_member);
+  }
+}
+
+void 
+XmppChatroomModuleImpl::FireMemberExited(const XmppChatroomMember* exited_member) {
+  // only fire if we're in the room
+  if (chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) {
+    if (chatroom_handler_)
+      chatroom_handler_->MemberExited(this, exited_member);
+  }
+}
+
+
+XmppReturnStatus 
+XmppChatroomModuleImpl::ServerChangedOtherPresence(const XmlElement& 
+                                                   presence_element) {
+  XmppReturnStatus xmpp_status = XMPP_RETURN_OK;
+  talk_base::scoped_ptr<XmppPresence> presence(XmppPresence::Create());
+  IFR(presence->set_raw_xml(&presence_element));
+
+  JidMemberMap::iterator pos = chatroom_jid_members_.find(presence->jid());
+
+  if (pos == chatroom_jid_members_.end()) {
+    if (presence->available() == XMPP_PRESENCE_AVAILABLE) { 
+      XmppChatroomMemberImpl* member = new XmppChatroomMemberImpl();
+      member->SetPresence(presence.get());
+      chatroom_jid_members_.insert(std::make_pair(member->member_jid(), member));
+      chatroom_jid_members_version_++;
+      FireMemberEntered(member);
+    }
+  } else {
+    XmppChatroomMemberImpl* member = pos->second;
+    if (presence->available() == XMPP_PRESENCE_AVAILABLE) {
+      member->SetPresence(presence.get());
+      chatroom_jid_members_version_++;
+      // $TODO - fire change
+    }
+    else if (presence->available() == XMPP_PRESENCE_UNAVAILABLE) {
+      chatroom_jid_members_.erase(pos);
+      chatroom_jid_members_version_++;
+      FireMemberExited(member);
+      delete member;
+    }
+  }
+
+  return xmpp_status;
+}
+
+XmppReturnStatus 
+XmppChatroomModuleImpl::ClientChangeMyPresence(XmppChatroomState new_state) {
+  return ChangePresence(new_state, NULL, false);
+}
+
+XmppReturnStatus 
+XmppChatroomModuleImpl::ServerChangeMyPresence(const XmlElement& presence) {
+   XmppChatroomState new_state;
+   
+   if (presence.HasAttr(QN_TYPE) == false) {
+      new_state = XMPP_CHATROOM_STATE_IN_ROOM;
+   } else {
+     new_state = XMPP_CHATROOM_STATE_NOT_IN_ROOM;
+   }
+  return ChangePresence(new_state, &presence, true);
+
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::ChangePresence(XmppChatroomState new_state, 
+                                       const XmlElement* presence, 
+                                       bool isServer) {
+  UNUSED(presence);
+  
+  XmppChatroomState old_state = chatroom_state_;
+  
+  // do nothing if state hasn't changed
+  if (old_state == new_state)
+    return XMPP_RETURN_OK;
+
+  // find the right transition description
+  StateTransitionDescription* transition_desc = NULL;
+  for (int i=0; i < ARRAY_SIZE(Transitions); i++) {
+    if (Transitions[i].old_state == old_state &&
+        Transitions[i].new_state == new_state) {
+        transition_desc = &Transitions[i];
+        break;
+    }
+  }
+
+  if (transition_desc == NULL) {
+    ASSERT(0);
+    return XMPP_RETURN_BADSTATE;
+  }
+
+  // we assert for any invalid transition states, and we'll
+  if (isServer) {
+    // $TODO send original stanza back to server and log an error?
+    ASSERT(transition_desc->is_valid_server_transition);
+  } else {
+    if (transition_desc->is_valid_client_transition == false) {
+      ASSERT(0);
+      return XMPP_RETURN_BADARGUMENT;
+    }
+  }
+
+  // set the new state and then fire any notifications to the handler
+  chatroom_state_ = new_state;
+
+  switch (transition_desc->transition_type) {
+    case TRANSITION_TYPE_ENTER_SUCCESS:
+      FireEnteredStatus(XMPP_CHATROOM_ENTERED_SUCCESS);
+      break;
+    case TRANSITION_TYPE_ENTER_FAILURE:
+      FireEnteredStatus(GetEnterFailureFromXml(presence));
+      break;
+    case TRANSITION_TYPE_EXIT_INVOLUNTARILY:
+      FireExitStatus(GetExitFailureFromXml(presence));
+      break;
+    case TRANSITION_TYPE_EXIT_VOLUNTARILY:
+      FireExitStatus(XMPP_CHATROOM_EXITED_REQUESTED);
+      break;
+    case TRANSITION_TYPE_NONE:
+      break;
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+XmppChatroomEnteredStatus 
+XmppChatroomModuleImpl::GetEnterFailureFromXml(const XmlElement* presence) {
+  XmppChatroomEnteredStatus status = XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED;
+  const XmlElement* error = presence->FirstNamed(QN_ERROR);
+  if (error != NULL && error->HasAttr(QN_CODE)) {
+    int code = atoi(error->Attr(QN_CODE).c_str());
+    switch (code) {
+      case 401: status = XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED; break;
+      case 403: status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED; break;
+      case 405: status = XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS; break;
+      case 407: status = XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER; break;
+      case 409: status = XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT; break;
+    }
+  }
+  return status;
+}
+
+XmppChatroomExitedStatus 
+XmppChatroomModuleImpl::GetExitFailureFromXml(const XmlElement* presence) {
+  XmppChatroomExitedStatus status = XMPP_CHATROOM_EXITED_UNSPECIFIED;
+  const XmlElement* error = presence->FirstNamed(QN_ERROR);
+  if (error != NULL && error->HasAttr(QN_CODE)) {
+    int code = atoi(error->Attr(QN_CODE).c_str());
+    switch (code) {
+      case 307: status = XMPP_CHATROOM_EXITED_KICKED; break;
+      case 322: status = XMPP_CHATROOM_EXITED_NOT_A_MEMBER; break;
+      case 332: status = XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN; break;
+    }
+  }
+  return status;
+}
+
+XmppReturnStatus
+XmppChatroomMemberImpl::SetPresence(const XmppPresence* presence) {
+  ASSERT(presence != NULL);
+  
+  // copy presence
+  presence_.reset(XmppPresence::Create());
+  presence_->set_raw_xml(presence->raw_xml());
+  return XMPP_RETURN_OK;
+}
+
+const Jid 
+XmppChatroomMemberImpl::member_jid() const {
+  return presence_->jid();
+}
+
+const Jid 
+XmppChatroomMemberImpl::full_jid() const {
+  return Jid("");
+}
+
+const std::string 
+XmppChatroomMemberImpl::name() const {
+  return member_jid().resource();
+}
+
+const XmppPresence* 
+XmppChatroomMemberImpl::presence() const {
+  return presence_.get();
+}
+
+
+// XmppChatroomMemberEnumeratorImpl --------------------------------------
+XmppChatroomMemberEnumeratorImpl::XmppChatroomMemberEnumeratorImpl(
+        XmppChatroomModuleImpl::JidMemberMap* map, int* map_version) {
+  map_ = map;
+  map_version_ = map_version;
+  map_version_created_ = *map_version_;
+  iterator_ = map->begin();
+  before_beginning_ = true;
+}
+
+XmppChatroomMember*
+XmppChatroomMemberEnumeratorImpl::current() {
+  if (IsValid() == false) {
+    return NULL;
+  } else if (IsBeforeBeginning() || IsAfterEnd()) {
+    return NULL;
+  } else {
+    return iterator_->second;
+  }
+}
+
+bool
+XmppChatroomMemberEnumeratorImpl::Prev() {
+  if (IsValid() == false) {
+    return false;
+  } else if (IsBeforeBeginning()) {
+    return false;
+  } else if (iterator_ == map_->begin()) {
+    before_beginning_ = true;
+    return false;
+  } else {
+    iterator_--;
+    return current() != NULL;
+  }
+}
+
+bool
+XmppChatroomMemberEnumeratorImpl::Next() {
+  if (IsValid() == false) {
+    return false;
+  } else if (IsBeforeBeginning()) {
+    before_beginning_ = false;
+    iterator_ = map_->begin();
+    return current() != NULL;
+  } else if (IsAfterEnd()) {
+    return false;
+  } else {
+    iterator_++;
+    return current() != NULL;
+  }
+}
+
+bool 
+XmppChatroomMemberEnumeratorImpl::IsValid() {
+  return map_version_created_ == *map_version_;
+}
+
+bool 
+XmppChatroomMemberEnumeratorImpl::IsBeforeBeginning() {
+  return before_beginning_;
+}
+
+bool 
+XmppChatroomMemberEnumeratorImpl::IsAfterEnd() {
+  return (iterator_ == map_->end());
+}
+
+
+
+} // namespace buzz
diff --git a/talk/xmpp/constants.cc b/talk/xmpp/constants.cc
index 1003f39..421fd8e 100644
--- a/talk/xmpp/constants.cc
+++ b/talk/xmpp/constants.cc
@@ -25,568 +25,532 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "talk/xmpp/constants.h"
+
 #include <string>
+
 #include "talk/base/basicdefs.h"
 #include "talk/xmllite/xmlconstants.h"
 #include "talk/xmllite/xmlelement.h"
 #include "talk/xmllite/qname.h"
 #include "talk/xmpp/jid.h"
-#include "talk/xmpp/constants.h"
+
 namespace buzz {
 
-const Jid JID_EMPTY(STR_EMPTY);
+// TODO: Remove static objects of complex types, particularly
+// Jid and QName.
 
-const std::string & Constants::ns_client() {
-  static const std::string ns_client_("jabber:client");
-  return ns_client_;
-}
+const char NS_CLIENT[] = "jabber:client";
+const char NS_SERVER[] = "jabber:server";
+const char NS_STREAM[] = "http://etherx.jabber.org/streams";
+const char NS_XSTREAM[] = "urn:ietf:params:xml:ns:xmpp-streams";
+const char NS_TLS[] = "urn:ietf:params:xml:ns:xmpp-tls";
+const char NS_SASL[] = "urn:ietf:params:xml:ns:xmpp-sasl";
+const char NS_BIND[] = "urn:ietf:params:xml:ns:xmpp-bind";
+const char NS_DIALBACK[] = "jabber:server:dialback";
+const char NS_SESSION[] = "urn:ietf:params:xml:ns:xmpp-session";
+const char NS_STANZA[] = "urn:ietf:params:xml:ns:xmpp-stanzas";
+const char NS_PRIVACY[] = "jabber:iq:privacy";
+const char NS_ROSTER[] = "jabber:iq:roster";
+const char NS_VCARD[] = "vcard-temp";
+const char NS_AVATAR_HASH[] = "google:avatar";
+const char NS_VCARD_UPDATE[] = "vcard-temp:x:update";
+const char STR_CLIENT[] = "client";
+const char STR_SERVER[] = "server";
+const char STR_STREAM[] = "stream";
 
-const std::string & Constants::ns_server() {
-  static const std::string ns_server_("jabber:server");
-  return ns_server_;
-}
+const char STR_GET[] = "get";
+const char STR_SET[] = "set";
+const char STR_RESULT[] = "result";
+const char STR_ERROR[] = "error";
 
-const std::string & Constants::ns_stream() {
-  static const std::string ns_stream_("http://etherx.jabber.org/streams");
-  return ns_stream_;
-}
+const char STR_FORM[] = "form";
+const char STR_SUBMIT[] = "submit";
+const char STR_TEXT_SINGLE[] = "text-single";
+const char STR_LIST_SINGLE[] = "list-single";
+const char STR_LIST_MULTI[] = "list-multi";
+const char STR_HIDDEN[] = "hidden";
+const char STR_FORM_TYPE[] = "FORM_TYPE";
 
-const std::string & Constants::ns_xstream() {
-  static const std::string ns_xstream_("urn:ietf:params:xml:ns:xmpp-streams");
-  return ns_xstream_;
-}
+const char STR_FROM[] = "from";
+const char STR_TO[] = "to";
+const char STR_BOTH[] = "both";
+const char STR_REMOVE[] = "remove";
 
-const std::string & Constants::ns_tls() {
-  static const std::string ns_tls_("urn:ietf:params:xml:ns:xmpp-tls");
-  return ns_tls_;
-}
-
-const std::string & Constants::ns_sasl() {
-  static const std::string ns_sasl_("urn:ietf:params:xml:ns:xmpp-sasl");
-  return ns_sasl_;
-}
-
-const std::string & Constants::ns_bind() {
-  static const std::string ns_bind_("urn:ietf:params:xml:ns:xmpp-bind");
-  return ns_bind_;
-}
-
-const std::string & Constants::ns_dialback() {
-  static const std::string ns_dialback_("jabber:server:dialback");
-  return ns_dialback_;
-}
-
-const std::string & Constants::ns_session() {
-  static const std::string ns_session_("urn:ietf:params:xml:ns:xmpp-session");
-  return ns_session_;
-}
-
-const std::string & Constants::ns_stanza() {
-  static const std::string ns_stanza_("urn:ietf:params:xml:ns:xmpp-stanzas");
-  return ns_stanza_;
-}
-
-const std::string & Constants::ns_privacy() {
-  static const std::string ns_privacy_("jabber:iq:privacy");
-  return ns_privacy_;
-}
-
-const std::string & Constants::ns_roster() {
-  static const std::string ns_roster_("jabber:iq:roster");
-  return ns_roster_;
-}
-
-const std::string & Constants::ns_vcard() {
-  static const std::string ns_vcard_("vcard-temp");
-  return ns_vcard_;
-}
-
-const std::string & Constants::ns_avatar_hash() {
-  static const std::string ns_avatar_hash_("google:avatar");
-  return ns_avatar_hash_;
-}
-
-const std::string & Constants::ns_vcard_update() {
-  static const std::string ns_vcard_update_("vcard-temp:x:update");
-  return ns_vcard_update_;
-}
-
-const std::string & Constants::str_client() {
-  static const std::string str_client_("client");
-  return str_client_;
-}
-
-const std::string & Constants::str_server() {
-  static const std::string str_server_("server");
-  return str_server_;
-}
-
-const std::string & Constants::str_stream() {
-  static const std::string str_stream_("stream");
-  return str_stream_;
-}
-
-const std::string STR_GET("get");
-const std::string STR_SET("set");
-const std::string STR_RESULT("result");
-const std::string STR_ERROR("error");
-
-const std::string STR_FORM("form");
-const std::string STR_SUBMIT("submit");
-const std::string STR_TEXT_SINGLE("text-single");
-const std::string STR_LIST_SINGLE("list-single");
-const std::string STR_LIST_MULTI("list-multi");
-const std::string STR_HIDDEN("hidden");
-const std::string STR_FORM_TYPE("FORM_TYPE");
-
-const std::string STR_FROM("from");
-const std::string STR_TO("to");
-const std::string STR_BOTH("both");
-const std::string STR_REMOVE("remove");
-
-const std::string STR_TYPE("type");
-const std::string STR_NAME("name");
-const std::string STR_ID("id");
-const std::string STR_JID("jid");
-const std::string STR_SUBSCRIPTION("subscription");
-const std::string STR_ASK("ask");
-const std::string STR_X("x");
-const std::string STR_GOOGLE_COM("google.com");
-const std::string STR_GMAIL_COM("gmail.com");
-const std::string STR_GOOGLEMAIL_COM("googlemail.com");
-const std::string STR_DEFAULT_DOMAIN("default.talk.google.com");
-const std::string STR_TALK_GOOGLE_COM("talk.google.com");
-const std::string STR_TALKX_L_GOOGLE_COM("talkx.l.google.com");
-const std::string STR_XMPP_GOOGLE_COM("xmpp.google.com");
-const std::string STR_XMPPX_L_GOOGLE_COM("xmppx.l.google.com");
+const char STR_TYPE[] = "type";
+const char STR_NAME[] = "name";
+const char STR_ID[] = "id";
+const char STR_JID[] = "jid";
+const char STR_SUBSCRIPTION[] = "subscription";
+const char STR_ASK[] = "ask";
+const char STR_X[] = "x";
+const char STR_GOOGLE_COM[] = "google.com";
+const char STR_GMAIL_COM[] = "gmail.com";
+const char STR_GOOGLEMAIL_COM[] = "googlemail.com";
+const char STR_DEFAULT_DOMAIN[] = "default.talk.google.com";
+const char STR_TALK_GOOGLE_COM[] = "talk.google.com";
+const char STR_TALKX_L_GOOGLE_COM[] = "talkx.l.google.com";
+const char STR_XMPP_GOOGLE_COM[] = "xmpp.google.com";
+const char STR_XMPPX_L_GOOGLE_COM[] = "xmppx.l.google.com";
 
 #ifdef FEATURE_ENABLE_VOICEMAIL
-const std::string STR_VOICEMAIL("voicemail");
-const std::string STR_OUTGOINGVOICEMAIL("outgoingvoicemail");
+const char STR_VOICEMAIL[] = "voicemail";
+const char STR_OUTGOINGVOICEMAIL[] = "outgoingvoicemail";
 #endif
 
-const std::string STR_UNAVAILABLE("unavailable");
+const char STR_UNAVAILABLE[] = "unavailable";
 
-const Jid JID_GOOGLE_MUC_LOOKUP("lookup.groupchat.google.com");
-const std::string STR_MUC_ROOMCONFIG_ROOMNAME("muc#roomconfig_roomname");
-const std::string STR_MUC_ROOMCONFIG_FEATURES("muc#roomconfig_features");
-const std::string STR_MUC_ROOM_FEATURE_ENTERPRISE("muc_enterprise");
-const std::string STR_MUC_ROOMCONFIG("http://jabber.org/protocol/muc#roomconfig");
+const char STR_GOOGLE_MUC_LOOKUP_JID[] = "lookup.groupchat.google.com";
+const char STR_MUC_ROOMCONFIG_ROOMNAME[] = "muc#roomconfig_roomname";
+const char STR_MUC_ROOMCONFIG_FEATURES[] = "muc#roomconfig_features";
+const char STR_MUC_ROOM_FEATURE_ENTERPRISE[] = "muc_enterprise";
+const char STR_MUC_ROOMCONFIG[] = "http://jabber.org/protocol/muc#roomconfig";
 
-const QName QN_STREAM_STREAM(true, NS_STREAM, STR_STREAM);
-const QName QN_STREAM_FEATURES(true, NS_STREAM, "features");
-const QName QN_STREAM_ERROR(true, NS_STREAM, "error");
+const StaticQName QN_STREAM_STREAM = { NS_STREAM, STR_STREAM };
+const StaticQName QN_STREAM_FEATURES = { NS_STREAM, "features" };
+const StaticQName QN_STREAM_ERROR = { NS_STREAM, "error" };
 
-const QName QN_XSTREAM_BAD_FORMAT(true, NS_XSTREAM, "bad-format");
-const QName QN_XSTREAM_BAD_NAMESPACE_PREFIX(true, NS_XSTREAM, "bad-namespace-prefix");
-const QName QN_XSTREAM_CONFLICT(true, NS_XSTREAM, "conflict");
-const QName QN_XSTREAM_CONNECTION_TIMEOUT(true, NS_XSTREAM, "connection-timeout");
-const QName QN_XSTREAM_HOST_GONE(true, NS_XSTREAM, "host-gone");
-const QName QN_XSTREAM_HOST_UNKNOWN(true, NS_XSTREAM, "host-unknown");
-const QName QN_XSTREAM_IMPROPER_ADDRESSIING(true, NS_XSTREAM, "improper-addressing");
-const QName QN_XSTREAM_INTERNAL_SERVER_ERROR(true, NS_XSTREAM, "internal-server-error");
-const QName QN_XSTREAM_INVALID_FROM(true, NS_XSTREAM, "invalid-from");
-const QName QN_XSTREAM_INVALID_ID(true, NS_XSTREAM, "invalid-id");
-const QName QN_XSTREAM_INVALID_NAMESPACE(true, NS_XSTREAM, "invalid-namespace");
-const QName QN_XSTREAM_INVALID_XML(true, NS_XSTREAM, "invalid-xml");
-const QName QN_XSTREAM_NOT_AUTHORIZED(true, NS_XSTREAM, "not-authorized");
-const QName QN_XSTREAM_POLICY_VIOLATION(true, NS_XSTREAM, "policy-violation");
-const QName QN_XSTREAM_REMOTE_CONNECTION_FAILED(true, NS_XSTREAM, "remote-connection-failed");
-const QName QN_XSTREAM_RESOURCE_CONSTRAINT(true, NS_XSTREAM, "resource-constraint");
-const QName QN_XSTREAM_RESTRICTED_XML(true, NS_XSTREAM, "restricted-xml");
-const QName QN_XSTREAM_SEE_OTHER_HOST(true, NS_XSTREAM, "see-other-host");
-const QName QN_XSTREAM_SYSTEM_SHUTDOWN(true, NS_XSTREAM, "system-shutdown");
-const QName QN_XSTREAM_UNDEFINED_CONDITION(true, NS_XSTREAM, "undefined-condition");
-const QName QN_XSTREAM_UNSUPPORTED_ENCODING(true, NS_XSTREAM, "unsupported-encoding");
-const QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE(true, NS_XSTREAM, "unsupported-stanza-type");
-const QName QN_XSTREAM_UNSUPPORTED_VERSION(true, NS_XSTREAM, "unsupported-version");
-const QName QN_XSTREAM_XML_NOT_WELL_FORMED(true, NS_XSTREAM, "xml-not-well-formed");
-const QName QN_XSTREAM_TEXT(true, NS_XSTREAM, "text");
+const StaticQName QN_XSTREAM_BAD_FORMAT = { NS_XSTREAM, "bad-format" };
+const StaticQName QN_XSTREAM_BAD_NAMESPACE_PREFIX =
+    { NS_XSTREAM, "bad-namespace-prefix" };
+const StaticQName QN_XSTREAM_CONFLICT = { NS_XSTREAM, "conflict" };
+const StaticQName QN_XSTREAM_CONNECTION_TIMEOUT =
+    { NS_XSTREAM, "connection-timeout" };
+const StaticQName QN_XSTREAM_HOST_GONE = { NS_XSTREAM, "host-gone" };
+const StaticQName QN_XSTREAM_HOST_UNKNOWN = { NS_XSTREAM, "host-unknown" };
+const StaticQName QN_XSTREAM_IMPROPER_ADDRESSIING =
+     { NS_XSTREAM, "improper-addressing" };
+const StaticQName QN_XSTREAM_INTERNAL_SERVER_ERROR =
+    { NS_XSTREAM, "internal-server-error" };
+const StaticQName QN_XSTREAM_INVALID_FROM = { NS_XSTREAM, "invalid-from" };
+const StaticQName QN_XSTREAM_INVALID_ID = { NS_XSTREAM, "invalid-id" };
+const StaticQName QN_XSTREAM_INVALID_NAMESPACE =
+    { NS_XSTREAM, "invalid-namespace" };
+const StaticQName QN_XSTREAM_INVALID_XML = { NS_XSTREAM, "invalid-xml" };
+const StaticQName QN_XSTREAM_NOT_AUTHORIZED = { NS_XSTREAM, "not-authorized" };
+const StaticQName QN_XSTREAM_POLICY_VIOLATION =
+    { NS_XSTREAM, "policy-violation" };
+const StaticQName QN_XSTREAM_REMOTE_CONNECTION_FAILED =
+    { NS_XSTREAM, "remote-connection-failed" };
+const StaticQName QN_XSTREAM_RESOURCE_CONSTRAINT =
+    { NS_XSTREAM, "resource-constraint" };
+const StaticQName QN_XSTREAM_RESTRICTED_XML = { NS_XSTREAM, "restricted-xml" };
+const StaticQName QN_XSTREAM_SEE_OTHER_HOST = { NS_XSTREAM, "see-other-host" };
+const StaticQName QN_XSTREAM_SYSTEM_SHUTDOWN =
+    { NS_XSTREAM, "system-shutdown" };
+const StaticQName QN_XSTREAM_UNDEFINED_CONDITION =
+    { NS_XSTREAM, "undefined-condition" };
+const StaticQName QN_XSTREAM_UNSUPPORTED_ENCODING =
+    { NS_XSTREAM, "unsupported-encoding" };
+const StaticQName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE =
+    { NS_XSTREAM, "unsupported-stanza-type" };
+const StaticQName QN_XSTREAM_UNSUPPORTED_VERSION =
+    { NS_XSTREAM, "unsupported-version" };
+const StaticQName QN_XSTREAM_XML_NOT_WELL_FORMED =
+    { NS_XSTREAM, "xml-not-well-formed" };
+const StaticQName QN_XSTREAM_TEXT = { NS_XSTREAM, "text" };
 
-const QName QN_TLS_STARTTLS(true, NS_TLS, "starttls");
-const QName QN_TLS_REQUIRED(true, NS_TLS, "required");
-const QName QN_TLS_PROCEED(true, NS_TLS, "proceed");
-const QName QN_TLS_FAILURE(true, NS_TLS, "failure");
+const StaticQName QN_TLS_STARTTLS = { NS_TLS, "starttls" };
+const StaticQName QN_TLS_REQUIRED = { NS_TLS, "required" };
+const StaticQName QN_TLS_PROCEED = { NS_TLS, "proceed" };
+const StaticQName QN_TLS_FAILURE = { NS_TLS, "failure" };
 
-const QName QN_SASL_MECHANISMS(true, NS_SASL, "mechanisms");
-const QName QN_SASL_MECHANISM(true, NS_SASL, "mechanism");
-const QName QN_SASL_AUTH(true, NS_SASL, "auth");
-const QName QN_SASL_CHALLENGE(true, NS_SASL, "challenge");
-const QName QN_SASL_RESPONSE(true, NS_SASL, "response");
-const QName QN_SASL_ABORT(true, NS_SASL, "abort");
-const QName QN_SASL_SUCCESS(true, NS_SASL, "success");
-const QName QN_SASL_FAILURE(true, NS_SASL, "failure");
-const QName QN_SASL_ABORTED(true, NS_SASL, "aborted");
-const QName QN_SASL_INCORRECT_ENCODING(true, NS_SASL, "incorrect-encoding");
-const QName QN_SASL_INVALID_AUTHZID(true, NS_SASL, "invalid-authzid");
-const QName QN_SASL_INVALID_MECHANISM(true, NS_SASL, "invalid-mechanism");
-const QName QN_SASL_MECHANISM_TOO_WEAK(true, NS_SASL, "mechanism-too-weak");
-const QName QN_SASL_NOT_AUTHORIZED(true, NS_SASL, "not-authorized");
-const QName QN_SASL_TEMPORARY_AUTH_FAILURE(true, NS_SASL, "temporary-auth-failure");
+const StaticQName QN_SASL_MECHANISMS = { NS_SASL, "mechanisms" };
+const StaticQName QN_SASL_MECHANISM = { NS_SASL, "mechanism" };
+const StaticQName QN_SASL_AUTH = { NS_SASL, "auth" };
+const StaticQName QN_SASL_CHALLENGE = { NS_SASL, "challenge" };
+const StaticQName QN_SASL_RESPONSE = { NS_SASL, "response" };
+const StaticQName QN_SASL_ABORT = { NS_SASL, "abort" };
+const StaticQName QN_SASL_SUCCESS = { NS_SASL, "success" };
+const StaticQName QN_SASL_FAILURE = { NS_SASL, "failure" };
+const StaticQName QN_SASL_ABORTED = { NS_SASL, "aborted" };
+const StaticQName QN_SASL_INCORRECT_ENCODING =
+    { NS_SASL, "incorrect-encoding" };
+const StaticQName QN_SASL_INVALID_AUTHZID = { NS_SASL, "invalid-authzid" };
+const StaticQName QN_SASL_INVALID_MECHANISM = { NS_SASL, "invalid-mechanism" };
+const StaticQName QN_SASL_MECHANISM_TOO_WEAK =
+    { NS_SASL, "mechanism-too-weak" };
+const StaticQName QN_SASL_NOT_AUTHORIZED = { NS_SASL, "not-authorized" };
+const StaticQName QN_SASL_TEMPORARY_AUTH_FAILURE =
+    { NS_SASL, "temporary-auth-failure" };
 
-const QName QN_DIALBACK_RESULT(true, NS_DIALBACK, "result");
-const QName QN_DIALBACK_VERIFY(true, NS_DIALBACK, "verify");
+// These are non-standard.
+const char NS_GOOGLE_AUTH_PROTOCOL[] =
+    "http://www.google.com/talk/protocol/auth";
+const StaticQName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT =
+    { NS_GOOGLE_AUTH_PROTOCOL, "client-uses-full-bind-result" };
+const char NS_GOOGLE_AUTH_OLD[] = "google:auth";
+const StaticQName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN =
+    { NS_GOOGLE_AUTH_PROTOCOL, "allow-non-google-login" };
 
-const QName QN_STANZA_BAD_REQUEST(true, NS_STANZA, "bad-request");
-const QName QN_STANZA_CONFLICT(true, NS_STANZA, "conflict");
-const QName QN_STANZA_FEATURE_NOT_IMPLEMENTED(true, NS_STANZA, "feature-not-implemented");
-const QName QN_STANZA_FORBIDDEN(true, NS_STANZA, "forbidden");
-const QName QN_STANZA_GONE(true, NS_STANZA, "gone");
-const QName QN_STANZA_INTERNAL_SERVER_ERROR(true, NS_STANZA, "internal-server-error");
-const QName QN_STANZA_ITEM_NOT_FOUND(true, NS_STANZA, "item-not-found");
-const QName QN_STANZA_JID_MALFORMED(true, NS_STANZA, "jid-malformed");
-const QName QN_STANZA_NOT_ACCEPTABLE(true, NS_STANZA, "not-acceptable");
-const QName QN_STANZA_NOT_ALLOWED(true, NS_STANZA, "not-allowed");
-const QName QN_STANZA_PAYMENT_REQUIRED(true, NS_STANZA, "payment-required");
-const QName QN_STANZA_RECIPIENT_UNAVAILABLE(true, NS_STANZA, "recipient-unavailable");
-const QName QN_STANZA_REDIRECT(true, NS_STANZA, "redirect");
-const QName QN_STANZA_REGISTRATION_REQUIRED(true, NS_STANZA, "registration-required");
-const QName QN_STANZA_REMOTE_SERVER_NOT_FOUND(true, NS_STANZA, "remote-server-not-found");
-const QName QN_STANZA_REMOTE_SERVER_TIMEOUT(true, NS_STANZA, "remote-server-timeout");
-const QName QN_STANZA_RESOURCE_CONSTRAINT(true, NS_STANZA, "resource-constraint");
-const QName QN_STANZA_SERVICE_UNAVAILABLE(true, NS_STANZA, "service-unavailable");
-const QName QN_STANZA_SUBSCRIPTION_REQUIRED(true, NS_STANZA, "subscription-required");
-const QName QN_STANZA_UNDEFINED_CONDITION(true, NS_STANZA, "undefined-condition");
-const QName QN_STANZA_UNEXPECTED_REQUEST(true, NS_STANZA, "unexpected-request");
-const QName QN_STANZA_TEXT(true, NS_STANZA, "text");
+const StaticQName QN_DIALBACK_RESULT = { NS_DIALBACK, "result" };
+const StaticQName QN_DIALBACK_VERIFY = { NS_DIALBACK, "verify" };
 
-const QName QN_BIND_BIND(true, NS_BIND, "bind");
-const QName QN_BIND_RESOURCE(true, NS_BIND, "resource");
-const QName QN_BIND_JID(true, NS_BIND, "jid");
+const StaticQName QN_STANZA_BAD_REQUEST = { NS_STANZA, "bad-request" };
+const StaticQName QN_STANZA_CONFLICT = { NS_STANZA, "conflict" };
+const StaticQName QN_STANZA_FEATURE_NOT_IMPLEMENTED =
+    { NS_STANZA, "feature-not-implemented" };
+const StaticQName QN_STANZA_FORBIDDEN = { NS_STANZA, "forbidden" };
+const StaticQName QN_STANZA_GONE = { NS_STANZA, "gone" };
+const StaticQName QN_STANZA_INTERNAL_SERVER_ERROR =
+    { NS_STANZA, "internal-server-error" };
+const StaticQName QN_STANZA_ITEM_NOT_FOUND = { NS_STANZA, "item-not-found" };
+const StaticQName QN_STANZA_JID_MALFORMED = { NS_STANZA, "jid-malformed" };
+const StaticQName QN_STANZA_NOT_ACCEPTABLE = { NS_STANZA, "not-acceptable" };
+const StaticQName QN_STANZA_NOT_ALLOWED = { NS_STANZA, "not-allowed" };
+const StaticQName QN_STANZA_PAYMENT_REQUIRED =
+    { NS_STANZA, "payment-required" };
+const StaticQName QN_STANZA_RECIPIENT_UNAVAILABLE =
+    { NS_STANZA, "recipient-unavailable" };
+const StaticQName QN_STANZA_REDIRECT = { NS_STANZA, "redirect" };
+const StaticQName QN_STANZA_REGISTRATION_REQUIRED =
+    { NS_STANZA, "registration-required" };
+const StaticQName QN_STANZA_REMOTE_SERVER_NOT_FOUND =
+    { NS_STANZA, "remote-server-not-found" };
+const StaticQName QN_STANZA_REMOTE_SERVER_TIMEOUT =
+    { NS_STANZA, "remote-server-timeout" };
+const StaticQName QN_STANZA_RESOURCE_CONSTRAINT =
+    { NS_STANZA, "resource-constraint" };
+const StaticQName QN_STANZA_SERVICE_UNAVAILABLE =
+    { NS_STANZA, "service-unavailable" };
+const StaticQName QN_STANZA_SUBSCRIPTION_REQUIRED =
+    { NS_STANZA, "subscription-required" };
+const StaticQName QN_STANZA_UNDEFINED_CONDITION =
+    { NS_STANZA, "undefined-condition" };
+const StaticQName QN_STANZA_UNEXPECTED_REQUEST =
+    { NS_STANZA, "unexpected-request" };
+const StaticQName QN_STANZA_TEXT = { NS_STANZA, "text" };
 
-const QName QN_MESSAGE(true, NS_CLIENT, "message");
-const QName QN_BODY(true, NS_CLIENT, "body");
-const QName QN_SUBJECT(true, NS_CLIENT, "subject");
-const QName QN_THREAD(true, NS_CLIENT, "thread");
-const QName QN_PRESENCE(true, NS_CLIENT, "presence");
-const QName QN_SHOW(true, NS_CLIENT, "show");
-const QName QN_STATUS(true, NS_CLIENT, "status");
-const QName QN_LANG(true, NS_CLIENT, "lang");
-const QName QN_PRIORITY(true, NS_CLIENT, "priority");
-const QName QN_IQ(true, NS_CLIENT, "iq");
-const QName QN_ERROR(true, NS_CLIENT, "error");
+const StaticQName QN_BIND_BIND = { NS_BIND, "bind" };
+const StaticQName QN_BIND_RESOURCE = { NS_BIND, "resource" };
+const StaticQName QN_BIND_JID = { NS_BIND, "jid" };
 
-const QName QN_SERVER_MESSAGE(true, NS_SERVER, "message");
-const QName QN_SERVER_BODY(true, NS_SERVER, "body");
-const QName QN_SERVER_SUBJECT(true, NS_SERVER, "subject");
-const QName QN_SERVER_THREAD(true, NS_SERVER, "thread");
-const QName QN_SERVER_PRESENCE(true, NS_SERVER, "presence");
-const QName QN_SERVER_SHOW(true, NS_SERVER, "show");
-const QName QN_SERVER_STATUS(true, NS_SERVER, "status");
-const QName QN_SERVER_LANG(true, NS_SERVER, "lang");
-const QName QN_SERVER_PRIORITY(true, NS_SERVER, "priority");
-const QName QN_SERVER_IQ(true, NS_SERVER, "iq");
-const QName QN_SERVER_ERROR(true, NS_SERVER, "error");
+const StaticQName QN_MESSAGE = { NS_CLIENT, "message" };
+const StaticQName QN_BODY = { NS_CLIENT, "body" };
+const StaticQName QN_SUBJECT = { NS_CLIENT, "subject" };
+const StaticQName QN_THREAD = { NS_CLIENT, "thread" };
+const StaticQName QN_PRESENCE = { NS_CLIENT, "presence" };
+const StaticQName QN_SHOW = { NS_CLIENT, "show" };
+const StaticQName QN_STATUS = { NS_CLIENT, "status" };
+const StaticQName QN_LANG = { NS_CLIENT, "lang" };
+const StaticQName QN_PRIORITY = { NS_CLIENT, "priority" };
+const StaticQName QN_IQ = { NS_CLIENT, "iq" };
+const StaticQName QN_ERROR = { NS_CLIENT, "error" };
 
-const QName QN_SESSION_SESSION(true, NS_SESSION, "session");
+const StaticQName QN_SERVER_MESSAGE = { NS_SERVER, "message" };
+const StaticQName QN_SERVER_BODY = { NS_SERVER, "body" };
+const StaticQName QN_SERVER_SUBJECT = { NS_SERVER, "subject" };
+const StaticQName QN_SERVER_THREAD = { NS_SERVER, "thread" };
+const StaticQName QN_SERVER_PRESENCE = { NS_SERVER, "presence" };
+const StaticQName QN_SERVER_SHOW = { NS_SERVER, "show" };
+const StaticQName QN_SERVER_STATUS = { NS_SERVER, "status" };
+const StaticQName QN_SERVER_LANG = { NS_SERVER, "lang" };
+const StaticQName QN_SERVER_PRIORITY = { NS_SERVER, "priority" };
+const StaticQName QN_SERVER_IQ = { NS_SERVER, "iq" };
+const StaticQName QN_SERVER_ERROR = { NS_SERVER, "error" };
 
-const QName QN_PRIVACY_QUERY(true, NS_PRIVACY, "query");
-const QName QN_PRIVACY_ACTIVE(true, NS_PRIVACY, "active");
-const QName QN_PRIVACY_DEFAULT(true, NS_PRIVACY, "default");
-const QName QN_PRIVACY_LIST(true, NS_PRIVACY, "list");
-const QName QN_PRIVACY_ITEM(true, NS_PRIVACY, "item");
-const QName QN_PRIVACY_IQ(true, NS_PRIVACY, "iq");
-const QName QN_PRIVACY_MESSAGE(true, NS_PRIVACY, "message");
-const QName QN_PRIVACY_PRESENCE_IN(true, NS_PRIVACY, "presence-in");
-const QName QN_PRIVACY_PRESENCE_OUT(true, NS_PRIVACY, "presence-out");
+const StaticQName QN_SESSION_SESSION = { NS_SESSION, "session" };
 
-const QName QN_ROSTER_QUERY(true, NS_ROSTER, "query");
-const QName QN_ROSTER_ITEM(true, NS_ROSTER, "item");
-const QName QN_ROSTER_GROUP(true, NS_ROSTER, "group");
+const StaticQName QN_PRIVACY_QUERY = { NS_PRIVACY, "query" };
+const StaticQName QN_PRIVACY_ACTIVE = { NS_PRIVACY, "active" };
+const StaticQName QN_PRIVACY_DEFAULT = { NS_PRIVACY, "default" };
+const StaticQName QN_PRIVACY_LIST = { NS_PRIVACY, "list" };
+const StaticQName QN_PRIVACY_ITEM = { NS_PRIVACY, "item" };
+const StaticQName QN_PRIVACY_IQ = { NS_PRIVACY, "iq" };
+const StaticQName QN_PRIVACY_MESSAGE = { NS_PRIVACY, "message" };
+const StaticQName QN_PRIVACY_PRESENCE_IN = { NS_PRIVACY, "presence-in" };
+const StaticQName QN_PRIVACY_PRESENCE_OUT = { NS_PRIVACY, "presence-out" };
 
-const QName QN_VCARD(true, NS_VCARD, "vCard");
-const QName QN_VCARD_FN(true, NS_VCARD, "FN");
-const QName QN_VCARD_PHOTO(true, NS_VCARD, "PHOTO");
-const QName QN_VCARD_PHOTO_BINVAL(true, NS_VCARD, "BINVAL");
-const QName QN_VCARD_AVATAR_HASH(true, NS_AVATAR_HASH, "hash");
-const QName QN_VCARD_AVATAR_HASH_MODIFIED(true, NS_AVATAR_HASH, "modified");
+const StaticQName QN_ROSTER_QUERY = { NS_ROSTER, "query" };
+const StaticQName QN_ROSTER_ITEM = { NS_ROSTER, "item" };
+const StaticQName QN_ROSTER_GROUP = { NS_ROSTER, "group" };
 
-const QName QN_NAME(true, STR_EMPTY, "name");
-const QName QN_AFFILIATION(true, STR_EMPTY, "affiliation");
-const QName QN_ROLE(true, STR_EMPTY, "role");
+const StaticQName QN_VCARD = { NS_VCARD, "vCard" };
+const StaticQName QN_VCARD_FN = { NS_VCARD, "FN" };
+const StaticQName QN_VCARD_PHOTO = { NS_VCARD, "PHOTO" };
+const StaticQName QN_VCARD_PHOTO_BINVAL = { NS_VCARD, "BINVAL" };
+const StaticQName QN_VCARD_AVATAR_HASH = { NS_AVATAR_HASH, "hash" };
+const StaticQName QN_VCARD_AVATAR_HASH_MODIFIED =
+    { NS_AVATAR_HASH, "modified" };
+
+const StaticQName QN_NAME = { STR_EMPTY, "name" };
+const StaticQName QN_AFFILIATION = { STR_EMPTY, "affiliation" };
+const StaticQName QN_ROLE = { STR_EMPTY, "role" };
 
 #if defined(FEATURE_ENABLE_PSTN)
-const QName QN_VCARD_TEL(true, NS_VCARD, "TEL");
-const QName QN_VCARD_VOICE(true, NS_VCARD, "VOICE");
-const QName QN_VCARD_HOME(true, NS_VCARD, "HOME");
-const QName QN_VCARD_WORK(true, NS_VCARD, "WORK");
-const QName QN_VCARD_CELL(true, NS_VCARD, "CELL");
-const QName QN_VCARD_NUMBER(true, NS_VCARD, "NUMBER");
+const StaticQName QN_VCARD_TEL = { NS_VCARD, "TEL" };
+const StaticQName QN_VCARD_VOICE = { NS_VCARD, "VOICE" };
+const StaticQName QN_VCARD_HOME = { NS_VCARD, "HOME" };
+const StaticQName QN_VCARD_WORK = { NS_VCARD, "WORK" };
+const StaticQName QN_VCARD_CELL = { NS_VCARD, "CELL" };
+const StaticQName QN_VCARD_NUMBER = { NS_VCARD, "NUMBER" };
 #endif
 
-const QName QN_XML_LANG(true, NS_XML, "lang");
+const StaticQName QN_XML_LANG = { NS_XML, "lang" };
 
-const QName QN_ENCODING(true, STR_EMPTY, STR_ENCODING);
-const QName QN_VERSION(true, STR_EMPTY, STR_VERSION);
-const QName QN_TO(true, STR_EMPTY, "to");
-const QName QN_FROM(true, STR_EMPTY, "from");
-const QName QN_TYPE(true, STR_EMPTY, "type");
-const QName QN_ID(true, STR_EMPTY, "id");
-const QName QN_CODE(true, STR_EMPTY, "code");
+const StaticQName QN_ENCODING = { STR_EMPTY, STR_ENCODING };
+const StaticQName QN_VERSION = { STR_EMPTY, STR_VERSION };
+const StaticQName QN_TO = { STR_EMPTY, "to" };
+const StaticQName QN_FROM = { STR_EMPTY, "from" };
+const StaticQName QN_TYPE = { STR_EMPTY, "type" };
+const StaticQName QN_ID = { STR_EMPTY, "id" };
+const StaticQName QN_CODE = { STR_EMPTY, "code" };
 
-const QName QN_VALUE(true, STR_EMPTY, "value");
-const QName QN_ACTION(true, STR_EMPTY, "action");
-const QName QN_ORDER(true, STR_EMPTY, "order");
-const QName QN_MECHANISM(true, STR_EMPTY, "mechanism");
-const QName QN_ASK(true, STR_EMPTY, "ask");
-const QName QN_JID(true, STR_EMPTY, "jid");
-const QName QN_NICK(true, STR_EMPTY, "nick");
-const QName QN_SUBSCRIPTION(true, STR_EMPTY, "subscription");
-const QName QN_TITLE1(true, STR_EMPTY, "title1");
-const QName QN_TITLE2(true, STR_EMPTY, "title2");
-const QName QN_SOURCE(true, STR_EMPTY, "source");
-const QName QN_TIME(true, STR_EMPTY, "time");
+const StaticQName QN_VALUE = { STR_EMPTY, "value" };
+const StaticQName QN_ACTION = { STR_EMPTY, "action" };
+const StaticQName QN_ORDER = { STR_EMPTY, "order" };
+const StaticQName QN_MECHANISM = { STR_EMPTY, "mechanism" };
+const StaticQName QN_ASK = { STR_EMPTY, "ask" };
+const StaticQName QN_JID = { STR_EMPTY, "jid" };
+const StaticQName QN_NICK = { STR_EMPTY, "nick" };
+const StaticQName QN_SUBSCRIPTION = { STR_EMPTY, "subscription" };
+const StaticQName QN_TITLE1 = { STR_EMPTY, "title1" };
+const StaticQName QN_TITLE2 = { STR_EMPTY, "title2" };
+const StaticQName QN_SOURCE = { STR_EMPTY, "source" };
+const StaticQName QN_TIME = { STR_EMPTY, "time" };
 
-const QName QN_XMLNS_CLIENT(true, NS_XMLNS, STR_CLIENT);
-const QName QN_XMLNS_SERVER(true, NS_XMLNS, STR_SERVER);
-const QName QN_XMLNS_STREAM(true, NS_XMLNS, STR_STREAM);
+const StaticQName QN_XMLNS_CLIENT = { NS_XMLNS, STR_CLIENT };
+const StaticQName QN_XMLNS_SERVER = { NS_XMLNS, STR_SERVER };
+const StaticQName QN_XMLNS_STREAM = { NS_XMLNS, STR_STREAM };
 
 
 // Presence
-const std::string STR_SHOW_AWAY("away");
-const std::string STR_SHOW_CHAT("chat");
-const std::string STR_SHOW_DND("dnd");
-const std::string STR_SHOW_XA("xa");
-const std::string STR_SHOW_OFFLINE("offline");
+const char STR_SHOW_AWAY[] = "away";
+const char STR_SHOW_CHAT[] = "chat";
+const char STR_SHOW_DND[] = "dnd";
+const char STR_SHOW_XA[] = "xa";
+const char STR_SHOW_OFFLINE[] = "offline";
 
 // Subscription
-const std::string STR_SUBSCRIBE("subscribe");
-const std::string STR_SUBSCRIBED("subscribed");
-const std::string STR_UNSUBSCRIBE("unsubscribe");
-const std::string STR_UNSUBSCRIBED("unsubscribed");
+const char STR_SUBSCRIBE[] = "subscribe";
+const char STR_SUBSCRIBED[] = "subscribed";
+const char STR_UNSUBSCRIBE[] = "unsubscribe";
+const char STR_UNSUBSCRIBED[] = "unsubscribed";
 
 // Google Invite
-const std::string NS_GOOGLE_INVITE("google:subscribe");
-const QName QN_INVITATION(true, NS_GOOGLE_INVITE, "invitation");
-const QName QN_INVITE_NAME(true, NS_GOOGLE_INVITE, "name");
-const QName QN_INVITE_SUBJECT(true, NS_GOOGLE_INVITE, "subject");
-const QName QN_INVITE_MESSAGE(true, NS_GOOGLE_INVITE, "body");
+const char NS_GOOGLE_INVITE[] = "google:subscribe";
+const StaticQName QN_INVITATION = { NS_GOOGLE_INVITE, "invitation" };
+const StaticQName QN_INVITE_NAME = { NS_GOOGLE_INVITE, "name" };
+const StaticQName QN_INVITE_SUBJECT = { NS_GOOGLE_INVITE, "subject" };
+const StaticQName QN_INVITE_MESSAGE = { NS_GOOGLE_INVITE, "body" };
 
 // PubSub: http://xmpp.org/extensions/xep-0060.html
-const std::string NS_PUBSUB("http://jabber.org/protocol/pubsub");
-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 char NS_PUBSUB[] = "http://jabber.org/protocol/pubsub";
+const StaticQName QN_PUBSUB = { NS_PUBSUB, "pubsub" };
+const StaticQName QN_PUBSUB_ITEMS = { NS_PUBSUB, "items" };
+const StaticQName QN_PUBSUB_ITEM = { NS_PUBSUB, "item" };
+const StaticQName QN_PUBSUB_PUBLISH = { NS_PUBSUB, "publish" };
+const StaticQName QN_PUBSUB_RETRACT = { NS_PUBSUB, "retract" };
+const StaticQName QN_ATTR_PUBLISHER = { STR_EMPTY, "publisher" };
 
-const std::string NS_PUBSUB_EVENT("http://jabber.org/protocol/pubsub#event");
-const QName QN_NODE(true, STR_EMPTY, "node");
-const QName QN_PUBSUB_EVENT(true, NS_PUBSUB_EVENT, "event");
-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 char NS_PUBSUB_EVENT[] = "http://jabber.org/protocol/pubsub#event";
+const StaticQName QN_NODE = { STR_EMPTY, "node" };
+const StaticQName QN_PUBSUB_EVENT = { NS_PUBSUB_EVENT, "event" };
+const StaticQName QN_PUBSUB_EVENT_ITEMS = { NS_PUBSUB_EVENT, "items" };
+const StaticQName QN_PUBSUB_EVENT_ITEM = { NS_PUBSUB_EVENT, "item" };
+const StaticQName QN_PUBSUB_EVENT_RETRACT = { NS_PUBSUB_EVENT, "retract" };
+const StaticQName QN_NOTIFY = { 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");
-
-
+const char NS_PRESENTER[] = "google:presenter";
+const StaticQName QN_PRESENTER_PRESENTER = { NS_PRESENTER, "presenter" };
+const StaticQName QN_PRESENTER_PRESENTATION_ITEM =
+    { NS_PRESENTER, "presentation-item" };
+const StaticQName QN_PRESENTER_PRESENTATION_TYPE =
+    { NS_PRESENTER, "presentation-type" };
+const StaticQName QN_PRESENTER_PRESENTATION_ID =
+    { NS_PRESENTER, "presentation-id" };
 
 // JEP 0030
-const QName QN_CATEGORY(true, STR_EMPTY, "category");
-const QName QN_VAR(true, STR_EMPTY, "var");
-const std::string NS_DISCO_INFO("http://jabber.org/protocol/disco#info");
-const std::string NS_DISCO_ITEMS("http://jabber.org/protocol/disco#items");
-const QName QN_DISCO_INFO_QUERY(true, NS_DISCO_INFO, "query");
-const QName QN_DISCO_IDENTITY(true, NS_DISCO_INFO, "identity");
-const QName QN_DISCO_FEATURE(true, NS_DISCO_INFO, "feature");
+const StaticQName QN_CATEGORY = { STR_EMPTY, "category" };
+const StaticQName QN_VAR = { STR_EMPTY, "var" };
+const char NS_DISCO_INFO[] = "http://jabber.org/protocol/disco#info";
+const char NS_DISCO_ITEMS[] = "http://jabber.org/protocol/disco#items";
+const StaticQName QN_DISCO_INFO_QUERY = { NS_DISCO_INFO, "query" };
+const StaticQName QN_DISCO_IDENTITY = { NS_DISCO_INFO, "identity" };
+const StaticQName QN_DISCO_FEATURE = { NS_DISCO_INFO, "feature" };
 
-const QName QN_DISCO_ITEMS_QUERY(true, NS_DISCO_ITEMS, "query");
-const QName QN_DISCO_ITEM(true, NS_DISCO_ITEMS, "item");
+const StaticQName QN_DISCO_ITEMS_QUERY = { NS_DISCO_ITEMS, "query" };
+const StaticQName QN_DISCO_ITEM = { NS_DISCO_ITEMS, "item" };
 
 // JEP 0020
-const std::string NS_FEATURE("http://jabber.org/protocol/feature-neg");
-const QName QN_FEATURE_FEATURE(true, NS_FEATURE, "feature");
+const char NS_FEATURE[] = "http://jabber.org/protocol/feature-neg";
+const StaticQName QN_FEATURE_FEATURE = { NS_FEATURE, "feature" };
 
 // JEP 0004
-const std::string NS_XDATA("jabber:x:data");
-const QName QN_XDATA_X(true, NS_XDATA, "x");
-const QName QN_XDATA_INSTRUCTIONS(true, NS_XDATA, "instructions");
-const QName QN_XDATA_TITLE(true, NS_XDATA, "title");
-const QName QN_XDATA_FIELD(true, NS_XDATA, "field");
-const QName QN_XDATA_REPORTED(true, NS_XDATA, "reported");
-const QName QN_XDATA_ITEM(true, NS_XDATA, "item");
-const QName QN_XDATA_DESC(true, NS_XDATA, "desc");
-const QName QN_XDATA_REQUIRED(true, NS_XDATA, "required");
-const QName QN_XDATA_VALUE(true, NS_XDATA, "value");
-const QName QN_XDATA_OPTION(true, NS_XDATA, "option");
+const char NS_XDATA[] = "jabber:x:data";
+const StaticQName QN_XDATA_X = { NS_XDATA, "x" };
+const StaticQName QN_XDATA_INSTRUCTIONS = { NS_XDATA, "instructions" };
+const StaticQName QN_XDATA_TITLE = { NS_XDATA, "title" };
+const StaticQName QN_XDATA_FIELD = { NS_XDATA, "field" };
+const StaticQName QN_XDATA_REPORTED = { NS_XDATA, "reported" };
+const StaticQName QN_XDATA_ITEM = { NS_XDATA, "item" };
+const StaticQName QN_XDATA_DESC = { NS_XDATA, "desc" };
+const StaticQName QN_XDATA_REQUIRED = { NS_XDATA, "required" };
+const StaticQName QN_XDATA_VALUE = { NS_XDATA, "value" };
+const StaticQName QN_XDATA_OPTION = { NS_XDATA, "option" };
 
 // JEP 0045
-const std::string NS_MUC("http://jabber.org/protocol/muc");
-const QName QN_MUC_X(true, NS_MUC, "x");
-const QName QN_MUC_ITEM(true, NS_MUC, "item");
-const QName QN_MUC_AFFILIATION(true, NS_MUC, "affiliation");
-const QName QN_MUC_ROLE(true, NS_MUC, "role");
-const std::string STR_AFFILIATION_NONE("none");
-const std::string STR_ROLE_PARTICIPANT("participant");
+const char NS_MUC[] = "http://jabber.org/protocol/muc";
+const StaticQName QN_MUC_X = { NS_MUC, "x" };
+const StaticQName QN_MUC_ITEM = { NS_MUC, "item" };
+const StaticQName QN_MUC_AFFILIATION = { NS_MUC, "affiliation" };
+const StaticQName QN_MUC_ROLE = { NS_MUC, "role" };
+const char STR_AFFILIATION_NONE[] = "none";
+const char STR_ROLE_PARTICIPANT[] = "participant";
 
-const std::string NS_MUC_OWNER("http://jabber.org/protocol/muc#owner");
-const QName QN_MUC_OWNER_QUERY(true, NS_MUC_OWNER, "query");
+const char NS_MUC_OWNER[] = "http://jabber.org/protocol/muc#owner";
+const StaticQName QN_MUC_OWNER_QUERY = { NS_MUC_OWNER, "query" };
 
-const std::string NS_MUC_USER("http://jabber.org/protocol/muc#user");
-const QName QN_MUC_USER_CONTINUE(true, NS_MUC_USER, "continue");
-const QName QN_MUC_USER_X(true, NS_MUC_USER, "x");
-const QName QN_MUC_USER_ITEM(true, NS_MUC_USER, "item");
-const QName QN_MUC_USER_STATUS(true, NS_MUC_USER, "status");
-
+const char NS_MUC_USER[] = "http://jabber.org/protocol/muc#user";
+const StaticQName QN_MUC_USER_CONTINUE = { NS_MUC_USER, "continue" };
+const StaticQName QN_MUC_USER_X = { NS_MUC_USER, "x" };
+const StaticQName QN_MUC_USER_ITEM = { NS_MUC_USER, "item" };
+const StaticQName QN_MUC_USER_STATUS = { NS_MUC_USER, "status" };
 
 // JEP 0055 - Jabber Search
-const std::string NS_SEARCH("jabber:iq:search");
-const QName QN_SEARCH_QUERY(true, NS_SEARCH, "query");
-const QName QN_SEARCH_ITEM(true, NS_SEARCH, "item");
-const QName QN_SEARCH_ROOM_NAME(true, NS_SEARCH, "room-name");
-const QName QN_SEARCH_ROOM_DOMAIN(true, NS_SEARCH, "room-domain");
-const QName QN_SEARCH_ROOM_JID(true, NS_SEARCH, "room-jid");
-
+const char NS_SEARCH[] = "jabber:iq:search";
+const StaticQName QN_SEARCH_QUERY = { NS_SEARCH, "query" };
+const StaticQName QN_SEARCH_ITEM = { NS_SEARCH, "item" };
+const StaticQName QN_SEARCH_ROOM_NAME = { NS_SEARCH, "room-name" };
+const StaticQName QN_SEARCH_ROOM_DOMAIN = { NS_SEARCH, "room-domain" };
+const StaticQName QN_SEARCH_ROOM_JID = { NS_SEARCH, "room-jid" };
 
 // JEP 0115
-const std::string NS_CAPS("http://jabber.org/protocol/caps");
-const QName QN_CAPS_C(true, NS_CAPS, "c");
-const QName QN_VER(true, STR_EMPTY, "ver");
-const QName QN_EXT(true, STR_EMPTY, "ext");
+const char NS_CAPS[] = "http://jabber.org/protocol/caps";
+const StaticQName QN_CAPS_C = { NS_CAPS, "c" };
+const StaticQName QN_VER = { STR_EMPTY, "ver" };
+const StaticQName QN_EXT = { STR_EMPTY, "ext" };
 
 // JEP 0153
-const std::string kNSVCard("vcard-temp:x:update");
-const QName kQnVCardX(true, kNSVCard, "x");
-const QName kQnVCardPhoto(true, kNSVCard, "photo");
+const char kNSVCard[] = "vcard-temp:x:update";
+const StaticQName kQnVCardX = { kNSVCard, "x" };
+const StaticQName kQnVCardPhoto = { kNSVCard, "photo" };
 
 // JEP 0172 User Nickname
-const std::string NS_NICKNAME("http://jabber.org/protocol/nick");
-const QName QN_NICKNAME(true, NS_NICKNAME, "nick");
-
+const char NS_NICKNAME[] = "http://jabber.org/protocol/nick";
+const StaticQName QN_NICKNAME = { NS_NICKNAME, "nick" };
 
 // JEP 0085 chat state
-const std::string NS_CHATSTATE("http://jabber.org/protocol/chatstates");
-const QName QN_CS_ACTIVE(true, NS_CHATSTATE, "active");
-const QName QN_CS_COMPOSING(true, NS_CHATSTATE, "composing");
-const QName QN_CS_PAUSED(true, NS_CHATSTATE, "paused");
-const QName QN_CS_INACTIVE(true, NS_CHATSTATE, "inactive");
-const QName QN_CS_GONE(true, NS_CHATSTATE, "gone");
+const char NS_CHATSTATE[] = "http://jabber.org/protocol/chatstates";
+const StaticQName QN_CS_ACTIVE = { NS_CHATSTATE, "active" };
+const StaticQName QN_CS_COMPOSING = { NS_CHATSTATE, "composing" };
+const StaticQName QN_CS_PAUSED = { NS_CHATSTATE, "paused" };
+const StaticQName QN_CS_INACTIVE = { NS_CHATSTATE, "inactive" };
+const StaticQName QN_CS_GONE = { NS_CHATSTATE, "gone" };
 
 // JEP 0091 Delayed Delivery
-const std::string kNSDelay("jabber:x:delay");
-const QName kQnDelayX(true, kNSDelay, "x");
-const QName kQnStamp(true, STR_EMPTY, "stamp");
+const char kNSDelay[] = "jabber:x:delay";
+const StaticQName kQnDelayX = { kNSDelay, "x" };
+const StaticQName kQnStamp = { STR_EMPTY, "stamp" };
 
 // Google time stamping (higher resolution)
-const std::string kNSTimestamp("google:timestamp");
-const QName kQnTime(true, kNSTimestamp, "time");
-const QName kQnMilliseconds(true, STR_EMPTY, "ms");
-
-
-// Event tracking
-#ifdef FEATURE_ENABLE_TRACKING
-const std::string NS_GOOGLE_EVENT_TRACKING("google:client-usability-testing");
-const QName QN_EVENT_TRACKING(true, NS_GOOGLE_EVENT_TRACKING, "usage-stats");
-const QName QN_EVENT_TRACKING_BRANDID(true, NS_GOOGLE_EVENT_TRACKING, "bid");
-const QName QN_EVENT_TRACKING_EVENT(true, NS_GOOGLE_EVENT_TRACKING, "event");
-const QName QN_EVENT_TRACKING_VARIABLE_KEY(true, STR_EMPTY, "key");
-const QName QN_EVENT_TRACKING_VARIABLE_VALUE(true, STR_EMPTY, "value");
-const QName QN_EVENT_TRACKING_VARIABLE_TIME(true, STR_EMPTY, "time");
-const QName QN_EVENT_TRACKING_EVENT_GROUP(true,
-                                          NS_GOOGLE_EVENT_TRACKING, "events");
-#endif
-
+const char kNSTimestamp[] = "google:timestamp";
+const StaticQName kQnTime = { kNSTimestamp, "time" };
+const StaticQName kQnMilliseconds = { STR_EMPTY, "ms" };
 
 // Jingle Info
-const std::string NS_JINGLE_INFO("google:jingleinfo");
-const QName QN_JINGLE_INFO_QUERY(true, NS_JINGLE_INFO, "query");
-const QName QN_JINGLE_INFO_STUN(true, NS_JINGLE_INFO, "stun");
-const QName QN_JINGLE_INFO_RELAY(true, NS_JINGLE_INFO, "relay");
-const QName QN_JINGLE_INFO_SERVER(true, NS_JINGLE_INFO, "server");
-const QName QN_JINGLE_INFO_TOKEN(true, NS_JINGLE_INFO, "token");
-const QName QN_JINGLE_INFO_HOST(true, STR_EMPTY, "host");
-const QName QN_JINGLE_INFO_TCP(true, STR_EMPTY, "tcp");
-const QName QN_JINGLE_INFO_UDP(true, STR_EMPTY, "udp");
-const QName QN_JINGLE_INFO_TCPSSL(true, STR_EMPTY, "tcpssl");
+const char NS_JINGLE_INFO[] = "google:jingleinfo";
+const StaticQName QN_JINGLE_INFO_QUERY = { NS_JINGLE_INFO, "query" };
+const StaticQName QN_JINGLE_INFO_STUN = { NS_JINGLE_INFO, "stun" };
+const StaticQName QN_JINGLE_INFO_RELAY = { NS_JINGLE_INFO, "relay" };
+const StaticQName QN_JINGLE_INFO_SERVER = { NS_JINGLE_INFO, "server" };
+const StaticQName QN_JINGLE_INFO_TOKEN = { NS_JINGLE_INFO, "token" };
+const StaticQName QN_JINGLE_INFO_HOST = { STR_EMPTY, "host" };
+const StaticQName QN_JINGLE_INFO_TCP = { STR_EMPTY, "tcp" };
+const StaticQName QN_JINGLE_INFO_UDP = { STR_EMPTY, "udp" };
+const StaticQName QN_JINGLE_INFO_TCPSSL = { STR_EMPTY, "tcpssl" };
 
 // Call Performance Logging
-const std::string NS_GOOGLE_CALLPERF_STATS("google:call-perf-stats");
-const QName QN_CALLPERF_STATS(true, NS_GOOGLE_CALLPERF_STATS, "callPerfStats");
-const QName QN_CALLPERF_SESSIONID(true, STR_EMPTY, "sessionId");
-const QName QN_CALLPERF_LOCALUSER(true, STR_EMPTY, "localUser");
-const QName QN_CALLPERF_REMOTEUSER(true, STR_EMPTY, "remoteUser");
-const QName QN_CALLPERF_STARTTIME(true, STR_EMPTY, "startTime");
-const QName QN_CALLPERF_CALL_LENGTH(true, STR_EMPTY, "callLength");
-const QName QN_CALLPERF_CALL_ACCEPTED(STR_EMPTY, "callAccepted");
-const QName QN_CALLPERF_CALL_ERROR_CODE(STR_EMPTY, "callErrorCode");
-const QName QN_CALLPERF_TERMINATE_CODE(STR_EMPTY, "terminateCode");
-const QName QN_CALLPERF_DATAPOINT(true, NS_GOOGLE_CALLPERF_STATS, "dataPoint");
-const QName QN_CALLPERF_DATAPOINT_TIME(true, STR_EMPTY, "timeStamp");
-const QName QN_CALLPERF_DATAPOINT_FRACTION_LOST(true, STR_EMPTY, "fraction_lost");
-const QName QN_CALLPERF_DATAPOINT_CUM_LOST(true, STR_EMPTY, "cum_lost");
-const QName QN_CALLPERF_DATAPOINT_EXT_MAX(true, STR_EMPTY, "ext_max");
-const QName QN_CALLPERF_DATAPOINT_JITTER(true, STR_EMPTY, "jitter");
-const QName QN_CALLPERF_DATAPOINT_RTT(true, STR_EMPTY, "RTT");
-const QName QN_CALLPERF_DATAPOINT_BYTES_R(true, STR_EMPTY, "bytesReceived");
-const QName QN_CALLPERF_DATAPOINT_PACKETS_R(true, STR_EMPTY, "packetsReceived");
-const QName QN_CALLPERF_DATAPOINT_BYTES_S(true, STR_EMPTY, "bytesSent");
-const QName QN_CALLPERF_DATAPOINT_PACKETS_S(true, STR_EMPTY, "packetsSent");
-const QName QN_CALLPERF_DATAPOINT_PROCESS_CPU(STR_EMPTY, "processCpu");
-const QName QN_CALLPERF_DATAPOINT_SYSTEM_CPU(STR_EMPTY, "systemCpu");
-const QName QN_CALLPERF_DATAPOINT_CPUS(STR_EMPTY, "cpus");
-const QName QN_CALLPERF_CONNECTION(true, NS_GOOGLE_CALLPERF_STATS, "connection");
-const QName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS(true, STR_EMPTY, "localAddress");
-const QName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS(true, STR_EMPTY, "remoteAddress");
-const QName QN_CALLPERF_CONNECTION_FLAGS(STR_EMPTY, "flags");
-const QName QN_CALLPERF_CONNECTION_RTT(STR_EMPTY, "rtt");
-const QName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S(
-    STR_EMPTY, "totalBytesSent");
-const QName QN_CALLPERF_CONNECTION_BYTES_SECOND_S(
-    STR_EMPTY, "bytesSecondSent");
-const QName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R(
-    STR_EMPTY, "totalBytesRecv");
-const QName QN_CALLPERF_CONNECTION_BYTES_SECOND_R(
-    STR_EMPTY, "bytesSecondRecv");
-const QName QN_CALLPERF_CANDIDATE(NS_GOOGLE_CALLPERF_STATS, "candidate");
-const QName QN_CALLPERF_CANDIDATE_ENDPOINT(STR_EMPTY, "endpoint");
-const QName QN_CALLPERF_CANDIDATE_PROTOCOL(STR_EMPTY, "protocol");
-const QName QN_CALLPERF_CANDIDATE_ADDRESS(STR_EMPTY, "address");
-const QName QN_CALLPERF_MEDIA(NS_GOOGLE_CALLPERF_STATS, "media");
-const QName QN_CALLPERF_MEDIA_DIRECTION(STR_EMPTY, "direction");
-const QName QN_CALLPERF_MEDIA_SSRC(STR_EMPTY, "SSRC");
-const QName QN_CALLPERF_MEDIA_ENERGY(STR_EMPTY, "energy");
-const QName QN_CALLPERF_MEDIA_FIR(STR_EMPTY, "fir");
-const QName QN_CALLPERF_MEDIA_NACK(STR_EMPTY, "nack");
-const QName QN_CALLPERF_MEDIA_FPS(STR_EMPTY, "fps");
-const QName QN_CALLPERF_MEDIA_FPS_NETWORK(STR_EMPTY, "fpsNetwork");
-const QName QN_CALLPERF_MEDIA_FPS_DECODED(STR_EMPTY, "fpsDecoded");
-const QName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE(
-    STR_EMPTY, "jitterBufferSize");
-const QName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE(
-    STR_EMPTY, "preferredJitterBufferSize");
-const QName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY(
-    STR_EMPTY, "totalPlayoutDelay");
+const char NS_GOOGLE_CALLPERF_STATS[] = "google:call-perf-stats";
+const StaticQName QN_CALLPERF_STATS =
+    { NS_GOOGLE_CALLPERF_STATS, "callPerfStats" };
+const StaticQName QN_CALLPERF_SESSIONID = { STR_EMPTY, "sessionId" };
+const StaticQName QN_CALLPERF_LOCALUSER = { STR_EMPTY, "localUser" };
+const StaticQName QN_CALLPERF_REMOTEUSER = { STR_EMPTY, "remoteUser" };
+const StaticQName QN_CALLPERF_STARTTIME = { STR_EMPTY, "startTime" };
+const StaticQName QN_CALLPERF_CALL_LENGTH = { STR_EMPTY, "callLength" };
+const StaticQName QN_CALLPERF_CALL_ACCEPTED = { STR_EMPTY, "callAccepted" };
+const StaticQName QN_CALLPERF_CALL_ERROR_CODE = { STR_EMPTY, "callErrorCode" };
+const StaticQName QN_CALLPERF_TERMINATE_CODE = { STR_EMPTY, "terminateCode" };
+const StaticQName QN_CALLPERF_DATAPOINT =
+    { NS_GOOGLE_CALLPERF_STATS, "dataPoint" };
+const StaticQName QN_CALLPERF_DATAPOINT_TIME = { STR_EMPTY, "timeStamp" };
+const StaticQName QN_CALLPERF_DATAPOINT_FRACTION_LOST =
+    { STR_EMPTY, "fraction_lost" };
+const StaticQName QN_CALLPERF_DATAPOINT_CUM_LOST = { STR_EMPTY, "cum_lost" };
+const StaticQName QN_CALLPERF_DATAPOINT_EXT_MAX = { STR_EMPTY, "ext_max" };
+const StaticQName QN_CALLPERF_DATAPOINT_JITTER = { STR_EMPTY, "jitter" };
+const StaticQName QN_CALLPERF_DATAPOINT_RTT = { STR_EMPTY, "RTT" };
+const StaticQName QN_CALLPERF_DATAPOINT_BYTES_R =
+    { STR_EMPTY, "bytesReceived" };
+const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_R =
+    { STR_EMPTY, "packetsReceived" };
+const StaticQName QN_CALLPERF_DATAPOINT_BYTES_S = { STR_EMPTY, "bytesSent" };
+const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_S =
+    { STR_EMPTY, "packetsSent" };
+const StaticQName QN_CALLPERF_DATAPOINT_PROCESS_CPU =
+    { STR_EMPTY, "processCpu" };
+const StaticQName QN_CALLPERF_DATAPOINT_SYSTEM_CPU = { STR_EMPTY, "systemCpu" };
+const StaticQName QN_CALLPERF_DATAPOINT_CPUS = { STR_EMPTY, "cpus" };
+const StaticQName QN_CALLPERF_CONNECTION =
+    { NS_GOOGLE_CALLPERF_STATS, "connection" };
+const StaticQName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS =
+    { STR_EMPTY, "localAddress" };
+const StaticQName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS =
+    { STR_EMPTY, "remoteAddress" };
+const StaticQName QN_CALLPERF_CONNECTION_FLAGS = { STR_EMPTY, "flags" };
+const StaticQName QN_CALLPERF_CONNECTION_RTT = { STR_EMPTY, "rtt" };
+const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S =
+    { STR_EMPTY, "totalBytesSent" };
+const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_S =
+    { STR_EMPTY, "bytesSecondSent" };
+const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R =
+    { STR_EMPTY, "totalBytesRecv" };
+const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_R =
+    { STR_EMPTY, "bytesSecondRecv" };
+const StaticQName QN_CALLPERF_CANDIDATE =
+    { NS_GOOGLE_CALLPERF_STATS, "candidate" };
+const StaticQName QN_CALLPERF_CANDIDATE_ENDPOINT = { STR_EMPTY, "endpoint" };
+const StaticQName QN_CALLPERF_CANDIDATE_PROTOCOL = { STR_EMPTY, "protocol" };
+const StaticQName QN_CALLPERF_CANDIDATE_ADDRESS = { STR_EMPTY, "address" };
+const StaticQName QN_CALLPERF_MEDIA = { NS_GOOGLE_CALLPERF_STATS, "media" };
+const StaticQName QN_CALLPERF_MEDIA_DIRECTION = { STR_EMPTY, "direction" };
+const StaticQName QN_CALLPERF_MEDIA_SSRC = { STR_EMPTY, "SSRC" };
+const StaticQName QN_CALLPERF_MEDIA_ENERGY = { STR_EMPTY, "energy" };
+const StaticQName QN_CALLPERF_MEDIA_FIR = { STR_EMPTY, "fir" };
+const StaticQName QN_CALLPERF_MEDIA_NACK = { STR_EMPTY, "nack" };
+const StaticQName QN_CALLPERF_MEDIA_FPS = { STR_EMPTY, "fps" };
+const StaticQName QN_CALLPERF_MEDIA_FPS_NETWORK = { STR_EMPTY, "fpsNetwork" };
+const StaticQName QN_CALLPERF_MEDIA_FPS_DECODED = { STR_EMPTY, "fpsDecoded" };
+const StaticQName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE =
+    { STR_EMPTY, "jitterBufferSize" };
+const StaticQName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE =
+    { STR_EMPTY, "preferredJitterBufferSize" };
+const StaticQName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY =
+    { STR_EMPTY, "totalPlayoutDelay" };
 
 // Muc invites.
-const QName QN_MUC_USER_INVITE(true, NS_MUC_USER, "invite");
+const StaticQName QN_MUC_USER_INVITE = { NS_MUC_USER, "invite" };
 
 // Multiway audio/video.
-const std::string NS_GOOGLE_MUC_USER("google:muc#user");
-const QName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA(true, NS_GOOGLE_MUC_USER, "available-media");
-const QName QN_GOOGLE_MUC_USER_ENTRY(true, NS_GOOGLE_MUC_USER, "entry");
-const QName QN_GOOGLE_MUC_USER_MEDIA(true, NS_GOOGLE_MUC_USER, "media");
-const QName QN_GOOGLE_MUC_USER_TYPE(true, NS_GOOGLE_MUC_USER, "type");
-const QName QN_GOOGLE_MUC_USER_SRC_ID(true, NS_GOOGLE_MUC_USER, "src-id");
-const QName QN_GOOGLE_MUC_USER_STATUS(true, NS_GOOGLE_MUC_USER, "status");
-const QName QN_LABEL(true, STR_EMPTY, "label");
+const char NS_GOOGLE_MUC_USER[] = "google:muc#user";
+const StaticQName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA =
+    { NS_GOOGLE_MUC_USER, "available-media" };
+const StaticQName QN_GOOGLE_MUC_USER_ENTRY = { NS_GOOGLE_MUC_USER, "entry" };
+const StaticQName QN_GOOGLE_MUC_USER_MEDIA = { NS_GOOGLE_MUC_USER, "media" };
+const StaticQName QN_GOOGLE_MUC_USER_TYPE = { NS_GOOGLE_MUC_USER, "type" };
+const StaticQName QN_GOOGLE_MUC_USER_SRC_ID = { NS_GOOGLE_MUC_USER, "src-id" };
+const StaticQName QN_GOOGLE_MUC_USER_STATUS = { NS_GOOGLE_MUC_USER, "status" };
+const StaticQName QN_LABEL = { 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");
+const char NS_GOOGLE_MUC_MEDIA[] = "google:muc#media";
+const StaticQName QN_GOOGLE_MUC_AUDIO_MUTE =
+    { NS_GOOGLE_MUC_MEDIA, "audio-mute" };
+const StaticQName QN_GOOGLE_MUC_VIDEO_MUTE =
+    { NS_GOOGLE_MUC_MEDIA, "video-mute" };
+const StaticQName QN_GOOGLE_MUC_RECORDING =
+    { NS_GOOGLE_MUC_MEDIA, "recording" };
+const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK = { NS_GOOGLE_MUC_MEDIA, "block" };
+const StaticQName QN_STATE_ATTR = { STR_EMPTY, "state" };
 
 }
diff --git a/talk/xmpp/constants.h b/talk/xmpp/constants.h
index 4409283..ad7d829 100644
--- a/talk/xmpp/constants.h
+++ b/talk/xmpp/constants.h
@@ -32,500 +32,472 @@
 #include "talk/xmllite/qname.h"
 #include "talk/xmpp/jid.h"
 
-
-#define NS_CLIENT Constants::ns_client()
-#define NS_SERVER Constants::ns_server()
-#define NS_STREAM Constants::ns_stream()
-#define NS_XSTREAM Constants::ns_xstream()
-#define NS_TLS Constants::ns_tls()
-#define NS_SASL Constants::ns_sasl()
-#define NS_BIND Constants::ns_bind()
-#define NS_DIALBACK Constants::ns_dialback()
-#define NS_SESSION Constants::ns_session()
-#define NS_STANZA Constants::ns_stanza()
-#define NS_PRIVACY Constants::ns_privacy()
-#define NS_ROSTER Constants::ns_roster()
-#define NS_VCARD Constants::ns_vcard()
-#define NS_AVATAR_HASH Constants::ns_avatar_hash()
-#define NS_VCARD_UPDATE Constants::ns_vcard_update()
-#define STR_CLIENT Constants::str_client()
-#define STR_SERVER Constants::str_server()
-#define STR_STREAM Constants::str_stream()
-
-
 namespace buzz {
 
-extern const Jid JID_EMPTY;
+extern const char NS_CLIENT[];
+extern const char NS_SERVER[];
+extern const char NS_STREAM[];
+extern const char NS_XSTREAM[];
+extern const char NS_TLS[];
+extern const char NS_SASL[];
+extern const char NS_BIND[];
+extern const char NS_DIALBACK[];
+extern const char NS_SESSION[];
+extern const char NS_STANZA[];
+extern const char NS_PRIVACY[];
+extern const char NS_ROSTER[];
+extern const char NS_VCARD[];
+extern const char NS_AVATAR_HASH[];
+extern const char NS_VCARD_UPDATE[];
+extern const char STR_CLIENT[];
+extern const char STR_SERVER[];
+extern const char STR_STREAM[];
 
-class Constants {
- public:
-  static const std::string & ns_client();
-  static const std::string & ns_server();
-  static const std::string & ns_stream();
-  static const std::string & ns_xstream();
-  static const std::string & ns_tls();
-  static const std::string & ns_sasl();
-  static const std::string & ns_bind();
-  static const std::string & ns_dialback();
-  static const std::string & ns_session();
-  static const std::string & ns_stanza();
-  static const std::string & ns_privacy();
-  static const std::string & ns_roster();
-  static const std::string & ns_vcard();
-  static const std::string & ns_avatar_hash();
-  static const std::string & ns_vcard_update();
+extern const char STR_GET[];
+extern const char STR_SET[];
+extern const char STR_RESULT[];
+extern const char STR_ERROR[];
 
-  static const std::string & str_client();
-  static const std::string & str_server();
-  static const std::string & str_stream();
-};
+extern const char STR_FORM[];
+extern const char STR_SUBMIT[];
+extern const char STR_TEXT_SINGLE[];
+extern const char STR_LIST_SINGLE[];
+extern const char STR_LIST_MULTI[];
+extern const char STR_HIDDEN[];
+extern const char STR_FORM_TYPE[];
 
-extern const std::string STR_GET;
-extern const std::string STR_SET;
-extern const std::string STR_RESULT;
-extern const std::string STR_ERROR;
+extern const char STR_FROM[];
+extern const char STR_TO[];
+extern const char STR_BOTH[];
+extern const char STR_REMOVE[];
 
-extern const std::string STR_FORM;
-extern const std::string STR_SUBMIT;
-extern const std::string STR_TEXT_SINGLE;
-extern const std::string STR_LIST_SINGLE;
-extern const std::string STR_LIST_MULTI;
-extern const std::string STR_HIDDEN;
-extern const std::string STR_FORM_TYPE;
-
-extern const std::string STR_FROM;
-extern const std::string STR_TO;
-extern const std::string STR_BOTH;
-extern const std::string STR_REMOVE;
-
-extern const std::string STR_TYPE;
-extern const std::string STR_NAME;
-extern const std::string STR_ID;
-extern const std::string STR_JID;
-extern const std::string STR_SUBSCRIPTION;
-extern const std::string STR_ASK;
-extern const std::string STR_X;
-extern const std::string STR_GOOGLE_COM;
-extern const std::string STR_GMAIL_COM;
-extern const std::string STR_GOOGLEMAIL_COM;
-extern const std::string STR_DEFAULT_DOMAIN;
-extern const std::string STR_TALK_GOOGLE_COM;
-extern const std::string STR_TALKX_L_GOOGLE_COM;
-extern const std::string STR_XMPP_GOOGLE_COM;
-extern const std::string STR_XMPPX_L_GOOGLE_COM;
+extern const char STR_TYPE[];
+extern const char STR_NAME[];
+extern const char STR_ID[];
+extern const char STR_JID[];
+extern const char STR_SUBSCRIPTION[];
+extern const char STR_ASK[];
+extern const char STR_X[];
+extern const char STR_GOOGLE_COM[];
+extern const char STR_GMAIL_COM[];
+extern const char STR_GOOGLEMAIL_COM[];
+extern const char STR_DEFAULT_DOMAIN[];
+extern const char STR_TALK_GOOGLE_COM[];
+extern const char STR_TALKX_L_GOOGLE_COM[];
+extern const char STR_XMPP_GOOGLE_COM[];
+extern const char STR_XMPPX_L_GOOGLE_COM[];
 
 #ifdef FEATURE_ENABLE_VOICEMAIL
-extern const std::string STR_VOICEMAIL;
-extern const std::string STR_OUTGOINGVOICEMAIL;
+extern const char STR_VOICEMAIL[];
+extern const char STR_OUTGOINGVOICEMAIL[];
 #endif
 
-extern const std::string STR_UNAVAILABLE;
+extern const char STR_UNAVAILABLE[];
 
-extern const Jid JID_GOOGLE_MUC_LOOKUP;
-extern const std::string STR_MUC_ROOMCONFIG_ROOMNAME;
-extern const std::string STR_MUC_ROOMCONFIG_FEATURES;
-extern const std::string STR_MUC_ROOM_FEATURE_ENTERPRISE;
+extern const char STR_GOOGLE_MUC_LOOKUP_JID[];
+extern const char STR_MUC_ROOMCONFIG_ROOMNAME[];
+extern const char STR_MUC_ROOMCONFIG_FEATURES[];
+extern const char STR_MUC_ROOM_FEATURE_ENTERPRISE[];
+extern const char STR_MUC_ROOMCONFIG[];
 
-extern const QName QN_STREAM_STREAM;
-extern const QName QN_STREAM_FEATURES;
-extern const QName QN_STREAM_ERROR;
+extern const StaticQName QN_STREAM_STREAM;
+extern const StaticQName QN_STREAM_FEATURES;
+extern const StaticQName QN_STREAM_ERROR;
 
-extern const QName QN_XSTREAM_BAD_FORMAT;
-extern const QName QN_XSTREAM_BAD_NAMESPACE_PREFIX;
-extern const QName QN_XSTREAM_CONFLICT;
-extern const QName QN_XSTREAM_CONNECTION_TIMEOUT;
-extern const QName QN_XSTREAM_HOST_GONE;
-extern const QName QN_XSTREAM_HOST_UNKNOWN;
-extern const QName QN_XSTREAM_IMPROPER_ADDRESSIING;
-extern const QName QN_XSTREAM_INTERNAL_SERVER_ERROR;
-extern const QName QN_XSTREAM_INVALID_FROM;
-extern const QName QN_XSTREAM_INVALID_ID;
-extern const QName QN_XSTREAM_INVALID_NAMESPACE;
-extern const QName QN_XSTREAM_INVALID_XML;
-extern const QName QN_XSTREAM_NOT_AUTHORIZED;
-extern const QName QN_XSTREAM_POLICY_VIOLATION;
-extern const QName QN_XSTREAM_REMOTE_CONNECTION_FAILED;
-extern const QName QN_XSTREAM_RESOURCE_CONSTRAINT;
-extern const QName QN_XSTREAM_RESTRICTED_XML;
-extern const QName QN_XSTREAM_SEE_OTHER_HOST;
-extern const QName QN_XSTREAM_SYSTEM_SHUTDOWN;
-extern const QName QN_XSTREAM_UNDEFINED_CONDITION;
-extern const QName QN_XSTREAM_UNSUPPORTED_ENCODING;
-extern const QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE;
-extern const QName QN_XSTREAM_UNSUPPORTED_VERSION;
-extern const QName QN_XSTREAM_XML_NOT_WELL_FORMED;
-extern const QName QN_XSTREAM_TEXT;
+extern const StaticQName QN_XSTREAM_BAD_FORMAT;
+extern const StaticQName QN_XSTREAM_BAD_NAMESPACE_PREFIX;
+extern const StaticQName QN_XSTREAM_CONFLICT;
+extern const StaticQName QN_XSTREAM_CONNECTION_TIMEOUT;
+extern const StaticQName QN_XSTREAM_HOST_GONE;
+extern const StaticQName QN_XSTREAM_HOST_UNKNOWN;
+extern const StaticQName QN_XSTREAM_IMPROPER_ADDRESSIING;
+extern const StaticQName QN_XSTREAM_INTERNAL_SERVER_ERROR;
+extern const StaticQName QN_XSTREAM_INVALID_FROM;
+extern const StaticQName QN_XSTREAM_INVALID_ID;
+extern const StaticQName QN_XSTREAM_INVALID_NAMESPACE;
+extern const StaticQName QN_XSTREAM_INVALID_XML;
+extern const StaticQName QN_XSTREAM_NOT_AUTHORIZED;
+extern const StaticQName QN_XSTREAM_POLICY_VIOLATION;
+extern const StaticQName QN_XSTREAM_REMOTE_CONNECTION_FAILED;
+extern const StaticQName QN_XSTREAM_RESOURCE_CONSTRAINT;
+extern const StaticQName QN_XSTREAM_RESTRICTED_XML;
+extern const StaticQName QN_XSTREAM_SEE_OTHER_HOST;
+extern const StaticQName QN_XSTREAM_SYSTEM_SHUTDOWN;
+extern const StaticQName QN_XSTREAM_UNDEFINED_CONDITION;
+extern const StaticQName QN_XSTREAM_UNSUPPORTED_ENCODING;
+extern const StaticQName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE;
+extern const StaticQName QN_XSTREAM_UNSUPPORTED_VERSION;
+extern const StaticQName QN_XSTREAM_XML_NOT_WELL_FORMED;
+extern const StaticQName QN_XSTREAM_TEXT;
 
-extern const QName QN_TLS_STARTTLS;
-extern const QName QN_TLS_REQUIRED;
-extern const QName QN_TLS_PROCEED;
-extern const QName QN_TLS_FAILURE;
+extern const StaticQName QN_TLS_STARTTLS;
+extern const StaticQName QN_TLS_REQUIRED;
+extern const StaticQName QN_TLS_PROCEED;
+extern const StaticQName QN_TLS_FAILURE;
 
-extern const QName QN_SASL_MECHANISMS;
-extern const QName QN_SASL_MECHANISM;
-extern const QName QN_SASL_AUTH;
-extern const QName QN_SASL_CHALLENGE;
-extern const QName QN_SASL_RESPONSE;
-extern const QName QN_SASL_ABORT;
-extern const QName QN_SASL_SUCCESS;
-extern const QName QN_SASL_FAILURE;
-extern const QName QN_SASL_ABORTED;
-extern const QName QN_SASL_INCORRECT_ENCODING;
-extern const QName QN_SASL_INVALID_AUTHZID;
-extern const QName QN_SASL_INVALID_MECHANISM;
-extern const QName QN_SASL_MECHANISM_TOO_WEAK;
-extern const QName QN_SASL_NOT_AUTHORIZED;
-extern const QName QN_SASL_TEMPORARY_AUTH_FAILURE;
+extern const StaticQName QN_SASL_MECHANISMS;
+extern const StaticQName QN_SASL_MECHANISM;
+extern const StaticQName QN_SASL_AUTH;
+extern const StaticQName QN_SASL_CHALLENGE;
+extern const StaticQName QN_SASL_RESPONSE;
+extern const StaticQName QN_SASL_ABORT;
+extern const StaticQName QN_SASL_SUCCESS;
+extern const StaticQName QN_SASL_FAILURE;
+extern const StaticQName QN_SASL_ABORTED;
+extern const StaticQName QN_SASL_INCORRECT_ENCODING;
+extern const StaticQName QN_SASL_INVALID_AUTHZID;
+extern const StaticQName QN_SASL_INVALID_MECHANISM;
+extern const StaticQName QN_SASL_MECHANISM_TOO_WEAK;
+extern const StaticQName QN_SASL_NOT_AUTHORIZED;
+extern const StaticQName QN_SASL_TEMPORARY_AUTH_FAILURE;
 
-extern const QName QN_DIALBACK_RESULT;
-extern const QName QN_DIALBACK_VERIFY;
+// These are non-standard.
+extern const char NS_GOOGLE_AUTH[];
+extern const char NS_GOOGLE_AUTH_PROTOCOL[];
+extern const StaticQName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT;
+extern const StaticQName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN;
 
-extern const QName QN_STANZA_BAD_REQUEST;
-extern const QName QN_STANZA_CONFLICT;
-extern const QName QN_STANZA_FEATURE_NOT_IMPLEMENTED;
-extern const QName QN_STANZA_FORBIDDEN;
-extern const QName QN_STANZA_GONE;
-extern const QName QN_STANZA_INTERNAL_SERVER_ERROR;
-extern const QName QN_STANZA_ITEM_NOT_FOUND;
-extern const QName QN_STANZA_JID_MALFORMED;
-extern const QName QN_STANZA_NOT_ACCEPTABLE;
-extern const QName QN_STANZA_NOT_ALLOWED;
-extern const QName QN_STANZA_PAYMENT_REQUIRED;
-extern const QName QN_STANZA_RECIPIENT_UNAVAILABLE;
-extern const QName QN_STANZA_REDIRECT;
-extern const QName QN_STANZA_REGISTRATION_REQUIRED;
-extern const QName QN_STANZA_REMOTE_SERVER_NOT_FOUND;
-extern const QName QN_STANZA_REMOTE_SERVER_TIMEOUT;
-extern const QName QN_STANZA_RESOURCE_CONSTRAINT;
-extern const QName QN_STANZA_SERVICE_UNAVAILABLE;
-extern const QName QN_STANZA_SUBSCRIPTION_REQUIRED;
-extern const QName QN_STANZA_UNDEFINED_CONDITION;
-extern const QName QN_STANZA_UNEXPECTED_REQUEST;
-extern const QName QN_STANZA_TEXT;
+extern const StaticQName QN_DIALBACK_RESULT;
+extern const StaticQName QN_DIALBACK_VERIFY;
 
-extern const QName QN_BIND_BIND;
-extern const QName QN_BIND_RESOURCE;
-extern const QName QN_BIND_JID;
+extern const StaticQName QN_STANZA_BAD_REQUEST;
+extern const StaticQName QN_STANZA_CONFLICT;
+extern const StaticQName QN_STANZA_FEATURE_NOT_IMPLEMENTED;
+extern const StaticQName QN_STANZA_FORBIDDEN;
+extern const StaticQName QN_STANZA_GONE;
+extern const StaticQName QN_STANZA_INTERNAL_SERVER_ERROR;
+extern const StaticQName QN_STANZA_ITEM_NOT_FOUND;
+extern const StaticQName QN_STANZA_JID_MALFORMED;
+extern const StaticQName QN_STANZA_NOT_ACCEPTABLE;
+extern const StaticQName QN_STANZA_NOT_ALLOWED;
+extern const StaticQName QN_STANZA_PAYMENT_REQUIRED;
+extern const StaticQName QN_STANZA_RECIPIENT_UNAVAILABLE;
+extern const StaticQName QN_STANZA_REDIRECT;
+extern const StaticQName QN_STANZA_REGISTRATION_REQUIRED;
+extern const StaticQName QN_STANZA_REMOTE_SERVER_NOT_FOUND;
+extern const StaticQName QN_STANZA_REMOTE_SERVER_TIMEOUT;
+extern const StaticQName QN_STANZA_RESOURCE_CONSTRAINT;
+extern const StaticQName QN_STANZA_SERVICE_UNAVAILABLE;
+extern const StaticQName QN_STANZA_SUBSCRIPTION_REQUIRED;
+extern const StaticQName QN_STANZA_UNDEFINED_CONDITION;
+extern const StaticQName QN_STANZA_UNEXPECTED_REQUEST;
+extern const StaticQName QN_STANZA_TEXT;
 
-extern const QName QN_MESSAGE;
-extern const QName QN_BODY;
-extern const QName QN_SUBJECT;
-extern const QName QN_THREAD;
-extern const QName QN_PRESENCE;
-extern const QName QN_SHOW;
-extern const QName QN_STATUS;
-extern const QName QN_LANG;
-extern const QName QN_PRIORITY;
-extern const QName QN_IQ;
-extern const QName QN_ERROR;
+extern const StaticQName QN_BIND_BIND;
+extern const StaticQName QN_BIND_RESOURCE;
+extern const StaticQName QN_BIND_JID;
 
-extern const QName QN_SERVER_MESSAGE;
-extern const QName QN_SERVER_BODY;
-extern const QName QN_SERVER_SUBJECT;
-extern const QName QN_SERVER_THREAD;
-extern const QName QN_SERVER_PRESENCE;
-extern const QName QN_SERVER_SHOW;
-extern const QName QN_SERVER_STATUS;
-extern const QName QN_SERVER_LANG;
-extern const QName QN_SERVER_PRIORITY;
-extern const QName QN_SERVER_IQ;
-extern const QName QN_SERVER_ERROR;
+extern const StaticQName QN_MESSAGE;
+extern const StaticQName QN_BODY;
+extern const StaticQName QN_SUBJECT;
+extern const StaticQName QN_THREAD;
+extern const StaticQName QN_PRESENCE;
+extern const StaticQName QN_SHOW;
+extern const StaticQName QN_STATUS;
+extern const StaticQName QN_LANG;
+extern const StaticQName QN_PRIORITY;
+extern const StaticQName QN_IQ;
+extern const StaticQName QN_ERROR;
 
-extern const QName QN_SESSION_SESSION;
+extern const StaticQName QN_SERVER_MESSAGE;
+extern const StaticQName QN_SERVER_BODY;
+extern const StaticQName QN_SERVER_SUBJECT;
+extern const StaticQName QN_SERVER_THREAD;
+extern const StaticQName QN_SERVER_PRESENCE;
+extern const StaticQName QN_SERVER_SHOW;
+extern const StaticQName QN_SERVER_STATUS;
+extern const StaticQName QN_SERVER_LANG;
+extern const StaticQName QN_SERVER_PRIORITY;
+extern const StaticQName QN_SERVER_IQ;
+extern const StaticQName QN_SERVER_ERROR;
 
-extern const QName QN_PRIVACY_QUERY;
-extern const QName QN_PRIVACY_ACTIVE;
-extern const QName QN_PRIVACY_DEFAULT;
-extern const QName QN_PRIVACY_LIST;
-extern const QName QN_PRIVACY_ITEM;
-extern const QName QN_PRIVACY_IQ;
-extern const QName QN_PRIVACY_MESSAGE;
-extern const QName QN_PRIVACY_PRESENCE_IN;
-extern const QName QN_PRIVACY_PRESENCE_OUT;
+extern const StaticQName QN_SESSION_SESSION;
 
-extern const QName QN_ROSTER_QUERY;
-extern const QName QN_ROSTER_ITEM;
-extern const QName QN_ROSTER_GROUP;
+extern const StaticQName QN_PRIVACY_QUERY;
+extern const StaticQName QN_PRIVACY_ACTIVE;
+extern const StaticQName QN_PRIVACY_DEFAULT;
+extern const StaticQName QN_PRIVACY_LIST;
+extern const StaticQName QN_PRIVACY_ITEM;
+extern const StaticQName QN_PRIVACY_IQ;
+extern const StaticQName QN_PRIVACY_MESSAGE;
+extern const StaticQName QN_PRIVACY_PRESENCE_IN;
+extern const StaticQName QN_PRIVACY_PRESENCE_OUT;
 
-extern const QName QN_VCARD;
-extern const QName QN_VCARD_FN;
-extern const QName QN_VCARD_PHOTO;
-extern const QName QN_VCARD_PHOTO_BINVAL;
-extern const QName QN_VCARD_AVATAR_HASH;
-extern const QName QN_VCARD_AVATAR_HASH_MODIFIED;
+extern const StaticQName QN_ROSTER_QUERY;
+extern const StaticQName QN_ROSTER_ITEM;
+extern const StaticQName QN_ROSTER_GROUP;
+
+extern const StaticQName QN_VCARD;
+extern const StaticQName QN_VCARD_FN;
+extern const StaticQName QN_VCARD_PHOTO;
+extern const StaticQName QN_VCARD_PHOTO_BINVAL;
+extern const StaticQName QN_VCARD_AVATAR_HASH;
+extern const StaticQName QN_VCARD_AVATAR_HASH_MODIFIED;
 
 #if defined(FEATURE_ENABLE_PSTN)
-extern const QName QN_VCARD_TEL;
-extern const QName QN_VCARD_VOICE;
-extern const QName QN_VCARD_HOME;
-extern const QName QN_VCARD_WORK;
-extern const QName QN_VCARD_CELL;
-extern const QName QN_VCARD_NUMBER;
+extern const StaticQName QN_VCARD_TEL;
+extern const StaticQName QN_VCARD_VOICE;
+extern const StaticQName QN_VCARD_HOME;
+extern const StaticQName QN_VCARD_WORK;
+extern const StaticQName QN_VCARD_CELL;
+extern const StaticQName QN_VCARD_NUMBER;
 #endif
 
 #if defined(FEATURE_ENABLE_RICHPROFILES)
-extern const QName QN_USER_PROFILE_QUERY;
-extern const QName QN_USER_PROFILE_URL;
+extern const StaticQName QN_USER_PROFILE_QUERY;
+extern const StaticQName QN_USER_PROFILE_URL;
 
-extern const QName QN_ATOM_FEED;
-extern const QName QN_ATOM_ENTRY;
-extern const QName QN_ATOM_TITLE;
-extern const QName QN_ATOM_ID;
-extern const QName QN_ATOM_MODIFIED;
-extern const QName QN_ATOM_IMAGE;
-extern const QName QN_ATOM_LINK;
-extern const QName QN_ATOM_HREF;
+extern const StaticQName QN_ATOM_FEED;
+extern const StaticQName QN_ATOM_ENTRY;
+extern const StaticQName QN_ATOM_TITLE;
+extern const StaticQName QN_ATOM_ID;
+extern const StaticQName QN_ATOM_MODIFIED;
+extern const StaticQName QN_ATOM_IMAGE;
+extern const StaticQName QN_ATOM_LINK;
+extern const StaticQName QN_ATOM_HREF;
 #endif
 
-extern const QName QN_XML_LANG;
+extern const StaticQName QN_XML_LANG;
 
-extern const QName QN_ENCODING;
-extern const QName QN_VERSION;
-extern const QName QN_TO;
-extern const QName QN_FROM;
-extern const QName QN_TYPE;
-extern const QName QN_ID;
-extern const QName QN_CODE;
-extern const QName QN_NAME;
-extern const QName QN_VALUE;
-extern const QName QN_ACTION;
-extern const QName QN_ORDER;
-extern const QName QN_MECHANISM;
-extern const QName QN_ASK;
-extern const QName QN_JID;
-extern const QName QN_NICK;
-extern const QName QN_SUBSCRIPTION;
-extern const QName QN_TITLE1;
-extern const QName QN_TITLE2;
-extern const QName QN_AFFILIATION;
-extern const QName QN_ROLE;
-extern const QName QN_TIME;
+extern const StaticQName QN_ENCODING;
+extern const StaticQName QN_VERSION;
+extern const StaticQName QN_TO;
+extern const StaticQName QN_FROM;
+extern const StaticQName QN_TYPE;
+extern const StaticQName QN_ID;
+extern const StaticQName QN_CODE;
+extern const StaticQName QN_NAME;
+extern const StaticQName QN_VALUE;
+extern const StaticQName QN_ACTION;
+extern const StaticQName QN_ORDER;
+extern const StaticQName QN_MECHANISM;
+extern const StaticQName QN_ASK;
+extern const StaticQName QN_JID;
+extern const StaticQName QN_NICK;
+extern const StaticQName QN_SUBSCRIPTION;
+extern const StaticQName QN_TITLE1;
+extern const StaticQName QN_TITLE2;
+extern const StaticQName QN_AFFILIATION;
+extern const StaticQName QN_ROLE;
+extern const StaticQName QN_TIME;
 
-
-extern const QName QN_XMLNS_CLIENT;
-extern const QName QN_XMLNS_SERVER;
-extern const QName QN_XMLNS_STREAM;
+extern const StaticQName QN_XMLNS_CLIENT;
+extern const StaticQName QN_XMLNS_SERVER;
+extern const StaticQName QN_XMLNS_STREAM;
 
 // Presence
-extern const std::string STR_SHOW_AWAY;
-extern const std::string STR_SHOW_CHAT;
-extern const std::string STR_SHOW_DND;
-extern const std::string STR_SHOW_XA;
-extern const std::string STR_SHOW_OFFLINE;
+extern const char STR_SHOW_AWAY[];
+extern const char STR_SHOW_CHAT[];
+extern const char STR_SHOW_DND[];
+extern const char STR_SHOW_XA[];
+extern const char STR_SHOW_OFFLINE[];
 
 // Subscription
-extern const std::string STR_SUBSCRIBE;
-extern const std::string STR_SUBSCRIBED;
-extern const std::string STR_UNSUBSCRIBE;
-extern const std::string STR_UNSUBSCRIBED;
+extern const char STR_SUBSCRIBE[];
+extern const char STR_SUBSCRIBED[];
+extern const char STR_UNSUBSCRIBE[];
+extern const char STR_UNSUBSCRIBED[];
 
 // Google Invite
-extern const std::string NS_GOOGLE_SUBSCRIBE;
-extern const QName QN_INVITATION;
-extern const QName QN_INVITE_NAME;
-extern const QName QN_INVITE_SUBJECT;
-extern const QName QN_INVITE_MESSAGE;
+extern const char NS_GOOGLE_SUBSCRIBE[];
+extern const StaticQName QN_INVITATION;
+extern const StaticQName QN_INVITE_NAME;
+extern const StaticQName QN_INVITE_SUBJECT;
+extern const StaticQName QN_INVITE_MESSAGE;
 
 // PubSub: http://xmpp.org/extensions/xep-0060.html
-extern const std::string NS_PUBSUB;
-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 char NS_PUBSUB[];
+extern const StaticQName QN_PUBSUB;
+extern const StaticQName QN_PUBSUB_ITEMS;
+extern const StaticQName QN_PUBSUB_ITEM;
+extern const StaticQName QN_PUBSUB_PUBLISH;
+extern const StaticQName QN_PUBSUB_RETRACT;
+extern const StaticQName QN_ATTR_PUBLISHER;
 
-extern const std::string NS_PUBSUB_EVENT;
-extern const QName QN_NODE;
-extern const QName QN_PUBSUB_EVENT;
-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 char NS_PUBSUB_EVENT[];
+extern const StaticQName QN_NODE;
+extern const StaticQName QN_PUBSUB_EVENT;
+extern const StaticQName QN_PUBSUB_EVENT_ITEMS;
+extern const StaticQName QN_PUBSUB_EVENT_ITEM;
+extern const StaticQName QN_PUBSUB_EVENT_RETRACT;
+extern const StaticQName 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;
-
-
-
+extern const char NS_PRESENTER[];
+extern const StaticQName QN_PRESENTER_PRESENTER;
+extern const StaticQName QN_PRESENTER_PRESENTATION_ITEM;
+extern const StaticQName QN_PRESENTER_PRESENTATION_TYPE;
+extern const StaticQName QN_PRESENTER_PRESENTATION_ID;
 
 // JEP 0030
-extern const QName QN_CATEGORY;
-extern const QName QN_VAR;
-extern const std::string NS_DISCO_INFO;
-extern const std::string NS_DISCO_ITEMS;
+extern const StaticQName QN_CATEGORY;
+extern const StaticQName QN_VAR;
+extern const char NS_DISCO_INFO[];
+extern const char NS_DISCO_ITEMS[];
 
-extern const QName QN_DISCO_INFO_QUERY;
-extern const QName QN_DISCO_IDENTITY;
-extern const QName QN_DISCO_FEATURE;
+extern const StaticQName QN_DISCO_INFO_QUERY;
+extern const StaticQName QN_DISCO_IDENTITY;
+extern const StaticQName QN_DISCO_FEATURE;
 
-extern const QName QN_DISCO_ITEMS_QUERY;
-extern const QName QN_DISCO_ITEM;
+extern const StaticQName QN_DISCO_ITEMS_QUERY;
+extern const StaticQName QN_DISCO_ITEM;
 
 // JEP 0020
-extern const std::string NS_FEATURE;
-extern const QName QN_FEATURE_FEATURE;
+extern const char NS_FEATURE[];
+extern const StaticQName QN_FEATURE_FEATURE;
 
 // JEP 0004
-extern const std::string NS_XDATA;
-extern const QName QN_XDATA_X;
-extern const QName QN_XDATA_INSTRUCTIONS;
-extern const QName QN_XDATA_TITLE;
-extern const QName QN_XDATA_FIELD;
-extern const QName QN_XDATA_REPORTED;
-extern const QName QN_XDATA_ITEM;
-extern const QName QN_XDATA_DESC;
-extern const QName QN_XDATA_REQUIRED;
-extern const QName QN_XDATA_VALUE;
-extern const QName QN_XDATA_OPTION;
+extern const char NS_XDATA[];
+extern const StaticQName QN_XDATA_X;
+extern const StaticQName QN_XDATA_INSTRUCTIONS;
+extern const StaticQName QN_XDATA_TITLE;
+extern const StaticQName QN_XDATA_FIELD;
+extern const StaticQName QN_XDATA_REPORTED;
+extern const StaticQName QN_XDATA_ITEM;
+extern const StaticQName QN_XDATA_DESC;
+extern const StaticQName QN_XDATA_REQUIRED;
+extern const StaticQName QN_XDATA_VALUE;
+extern const StaticQName QN_XDATA_OPTION;
 
 // JEP 0045
-extern const std::string NS_MUC;
-extern const QName QN_MUC_X;
-extern const QName QN_MUC_ITEM;
-extern const QName QN_MUC_AFFILIATION;
-extern const QName QN_MUC_ROLE;
-extern const std::string STR_AFFILIATION_NONE;
-extern const std::string STR_ROLE_PARTICIPANT;
+extern const char NS_MUC[];
+extern const StaticQName QN_MUC_X;
+extern const StaticQName QN_MUC_ITEM;
+extern const StaticQName QN_MUC_AFFILIATION;
+extern const StaticQName QN_MUC_ROLE;
+extern const char STR_AFFILIATION_NONE[];
+extern const char STR_ROLE_PARTICIPANT[];
 
-extern const std::string NS_MUC_OWNER;
-extern const QName QN_MUC_OWNER_QUERY;
+extern const char NS_MUC_OWNER[];
+extern const StaticQName QN_MUC_OWNER_QUERY;
 
-extern const std::string NS_MUC_USER;
-extern const QName QN_MUC_USER_CONTINUE;
-extern const QName QN_MUC_USER_X;
-extern const QName QN_MUC_USER_ITEM;
-extern const QName QN_MUC_USER_STATUS;
-
+extern const char NS_MUC_USER[];
+extern const StaticQName QN_MUC_USER_CONTINUE;
+extern const StaticQName QN_MUC_USER_X;
+extern const StaticQName QN_MUC_USER_ITEM;
+extern const StaticQName QN_MUC_USER_STATUS;
 
 // JEP 0055 - Jabber Search
-extern const std::string NS_SEARCH;
-extern const QName QN_SEARCH_QUERY;
-extern const QName QN_SEARCH_ITEM;
-extern const QName QN_SEARCH_ROOM_NAME;
-extern const QName QN_SEARCH_ROOM_JID;
-extern const QName QN_SEARCH_ROOM_DOMAIN;
-
+extern const char NS_SEARCH[];
+extern const StaticQName QN_SEARCH_QUERY;
+extern const StaticQName QN_SEARCH_ITEM;
+extern const StaticQName QN_SEARCH_ROOM_NAME;
+extern const StaticQName QN_SEARCH_ROOM_JID;
+extern const StaticQName QN_SEARCH_ROOM_DOMAIN;
 
 // JEP 0115
-extern const std::string NS_CAPS;
-extern const QName QN_CAPS_C;
-extern const QName QN_VER;
-extern const QName QN_EXT;
+extern const char NS_CAPS[];
+extern const StaticQName QN_CAPS_C;
+extern const StaticQName QN_VER;
+extern const StaticQName QN_EXT;
 
 
 // Avatar - JEP 0153
-extern const std::string kNSVCard;
-extern const QName kQnVCardX;
-extern const QName kQnVCardPhoto;
+extern const char kNSVCard[];
+extern const StaticQName kQnVCardX;
+extern const StaticQName kQnVCardPhoto;
 
 // JEP 0172 User Nickname
-extern const std::string NS_NICKNAME;
-extern const QName QN_NICKNAME;
-
+extern const char NS_NICKNAME[];
+extern const StaticQName QN_NICKNAME;
 
 // JEP 0085 chat state
-extern const std::string NS_CHATSTATE;
-extern const QName QN_CS_ACTIVE;
-extern const QName QN_CS_COMPOSING;
-extern const QName QN_CS_PAUSED;
-extern const QName QN_CS_INACTIVE;
-extern const QName QN_CS_GONE;
+extern const char NS_CHATSTATE[];
+extern const StaticQName QN_CS_ACTIVE;
+extern const StaticQName QN_CS_COMPOSING;
+extern const StaticQName QN_CS_PAUSED;
+extern const StaticQName QN_CS_INACTIVE;
+extern const StaticQName QN_CS_GONE;
 
 // JEP 0091 Delayed Delivery
-extern const std::string kNSDelay;
-extern const QName kQnDelayX;
-extern const QName kQnStamp;
+extern const char kNSDelay[];
+extern const StaticQName kQnDelayX;
+extern const StaticQName kQnStamp;
 
 // Google time stamping (higher resolution)
-extern const std::string kNSTimestamp;
-extern const QName kQnTime;
-extern const QName kQnMilliseconds;
+extern const char kNSTimestamp[];
+extern const StaticQName kQnTime;
+extern const StaticQName kQnMilliseconds;
 
+extern const char NS_JINGLE_INFO[];
+extern const StaticQName QN_JINGLE_INFO_QUERY;
+extern const StaticQName QN_JINGLE_INFO_STUN;
+extern const StaticQName QN_JINGLE_INFO_RELAY;
+extern const StaticQName QN_JINGLE_INFO_SERVER;
+extern const StaticQName QN_JINGLE_INFO_TOKEN;
+extern const StaticQName QN_JINGLE_INFO_HOST;
+extern const StaticQName QN_JINGLE_INFO_TCP;
+extern const StaticQName QN_JINGLE_INFO_UDP;
+extern const StaticQName QN_JINGLE_INFO_TCPSSL;
 
-extern const std::string NS_JINGLE_INFO;
-extern const QName QN_JINGLE_INFO_QUERY;
-extern const QName QN_JINGLE_INFO_STUN;
-extern const QName QN_JINGLE_INFO_RELAY;
-extern const QName QN_JINGLE_INFO_SERVER;
-extern const QName QN_JINGLE_INFO_TOKEN;
-extern const QName QN_JINGLE_INFO_HOST;
-extern const QName QN_JINGLE_INFO_TCP;
-extern const QName QN_JINGLE_INFO_UDP;
-extern const QName QN_JINGLE_INFO_TCPSSL;
-
-extern const std::string NS_GOOGLE_CALLPERF_STATS;
-extern const QName QN_CALLPERF_STATS;
-extern const QName QN_CALLPERF_SESSIONID;
-extern const QName QN_CALLPERF_LOCALUSER;
-extern const QName QN_CALLPERF_REMOTEUSER;
-extern const QName QN_CALLPERF_STARTTIME;
-extern const QName QN_CALLPERF_CALL_LENGTH;
-extern const QName QN_CALLPERF_CALL_ACCEPTED;
-extern const QName QN_CALLPERF_CALL_ERROR_CODE;
-extern const QName QN_CALLPERF_TERMINATE_CODE;
-extern const QName QN_CALLPERF_DATAPOINT;
-extern const QName QN_CALLPERF_DATAPOINT_TIME;
-extern const QName QN_CALLPERF_DATAPOINT_FRACTION_LOST;
-extern const QName QN_CALLPERF_DATAPOINT_CUM_LOST;
-extern const QName QN_CALLPERF_DATAPOINT_EXT_MAX;
-extern const QName QN_CALLPERF_DATAPOINT_JITTER;
-extern const QName QN_CALLPERF_DATAPOINT_RTT;
-extern const QName QN_CALLPERF_DATAPOINT_BYTES_R;
-extern const QName QN_CALLPERF_DATAPOINT_PACKETS_R;
-extern const QName QN_CALLPERF_DATAPOINT_BYTES_S;
-extern const QName QN_CALLPERF_DATAPOINT_PACKETS_S;
-extern const QName QN_CALLPERF_DATAPOINT_PROCESS_CPU;
-extern const QName QN_CALLPERF_DATAPOINT_SYSTEM_CPU;
-extern const QName QN_CALLPERF_DATAPOINT_CPUS;
-extern const QName QN_CALLPERF_CONNECTION;
-extern const QName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS;
-extern const QName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS;
-extern const QName QN_CALLPERF_CONNECTION_FLAGS;
-extern const QName QN_CALLPERF_CONNECTION_RTT;
-extern const QName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S;
-extern const QName QN_CALLPERF_CONNECTION_BYTES_SECOND_S;
-extern const QName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R;
-extern const QName QN_CALLPERF_CONNECTION_BYTES_SECOND_R;
-extern const QName QN_CALLPERF_CANDIDATE;
-extern const QName QN_CALLPERF_CANDIDATE_ENDPOINT;
-extern const QName QN_CALLPERF_CANDIDATE_PROTOCOL;
-extern const QName QN_CALLPERF_CANDIDATE_ADDRESS;
-extern const QName QN_CALLPERF_MEDIA;
-extern const QName QN_CALLPERF_MEDIA_DIRECTION;
-extern const QName QN_CALLPERF_MEDIA_SSRC;
-extern const QName QN_CALLPERF_MEDIA_ENERGY;
-extern const QName QN_CALLPERF_MEDIA_FIR;
-extern const QName QN_CALLPERF_MEDIA_NACK;
-extern const QName QN_CALLPERF_MEDIA_FPS;
-extern const QName QN_CALLPERF_MEDIA_FPS_NETWORK;
-extern const QName QN_CALLPERF_MEDIA_FPS_DECODED;
-extern const QName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE;
-extern const QName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE;
-extern const QName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY;
+extern const char NS_GOOGLE_CALLPERF_STATS[];
+extern const StaticQName QN_CALLPERF_STATS;
+extern const StaticQName QN_CALLPERF_SESSIONID;
+extern const StaticQName QN_CALLPERF_LOCALUSER;
+extern const StaticQName QN_CALLPERF_REMOTEUSER;
+extern const StaticQName QN_CALLPERF_STARTTIME;
+extern const StaticQName QN_CALLPERF_CALL_LENGTH;
+extern const StaticQName QN_CALLPERF_CALL_ACCEPTED;
+extern const StaticQName QN_CALLPERF_CALL_ERROR_CODE;
+extern const StaticQName QN_CALLPERF_TERMINATE_CODE;
+extern const StaticQName QN_CALLPERF_DATAPOINT;
+extern const StaticQName QN_CALLPERF_DATAPOINT_TIME;
+extern const StaticQName QN_CALLPERF_DATAPOINT_FRACTION_LOST;
+extern const StaticQName QN_CALLPERF_DATAPOINT_CUM_LOST;
+extern const StaticQName QN_CALLPERF_DATAPOINT_EXT_MAX;
+extern const StaticQName QN_CALLPERF_DATAPOINT_JITTER;
+extern const StaticQName QN_CALLPERF_DATAPOINT_RTT;
+extern const StaticQName QN_CALLPERF_DATAPOINT_BYTES_R;
+extern const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_R;
+extern const StaticQName QN_CALLPERF_DATAPOINT_BYTES_S;
+extern const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_S;
+extern const StaticQName QN_CALLPERF_DATAPOINT_PROCESS_CPU;
+extern const StaticQName QN_CALLPERF_DATAPOINT_SYSTEM_CPU;
+extern const StaticQName QN_CALLPERF_DATAPOINT_CPUS;
+extern const StaticQName QN_CALLPERF_CONNECTION;
+extern const StaticQName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS;
+extern const StaticQName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS;
+extern const StaticQName QN_CALLPERF_CONNECTION_FLAGS;
+extern const StaticQName QN_CALLPERF_CONNECTION_RTT;
+extern const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S;
+extern const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_S;
+extern const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R;
+extern const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_R;
+extern const StaticQName QN_CALLPERF_CANDIDATE;
+extern const StaticQName QN_CALLPERF_CANDIDATE_ENDPOINT;
+extern const StaticQName QN_CALLPERF_CANDIDATE_PROTOCOL;
+extern const StaticQName QN_CALLPERF_CANDIDATE_ADDRESS;
+extern const StaticQName QN_CALLPERF_MEDIA;
+extern const StaticQName QN_CALLPERF_MEDIA_DIRECTION;
+extern const StaticQName QN_CALLPERF_MEDIA_SSRC;
+extern const StaticQName QN_CALLPERF_MEDIA_ENERGY;
+extern const StaticQName QN_CALLPERF_MEDIA_FIR;
+extern const StaticQName QN_CALLPERF_MEDIA_NACK;
+extern const StaticQName QN_CALLPERF_MEDIA_FPS;
+extern const StaticQName QN_CALLPERF_MEDIA_FPS_NETWORK;
+extern const StaticQName QN_CALLPERF_MEDIA_FPS_DECODED;
+extern const StaticQName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE;
+extern const StaticQName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE;
+extern const StaticQName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY;
 
 // Muc invites.
-extern const QName QN_MUC_USER_INVITE;
+extern const StaticQName QN_MUC_USER_INVITE;
 
 // Multiway audio/video.
-extern const std::string NS_GOOGLE_MUC_USER;
-extern const QName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA;
-extern const QName QN_GOOGLE_MUC_USER_ENTRY;
-extern const QName QN_GOOGLE_MUC_USER_MEDIA;
-extern const QName QN_GOOGLE_MUC_USER_TYPE;
-extern const QName QN_GOOGLE_MUC_USER_SRC_ID;
-extern const QName QN_GOOGLE_MUC_USER_STATUS;
-extern const QName QN_LABEL;
+extern const char NS_GOOGLE_MUC_USER[];
+extern const StaticQName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA;
+extern const StaticQName QN_GOOGLE_MUC_USER_ENTRY;
+extern const StaticQName QN_GOOGLE_MUC_USER_MEDIA;
+extern const StaticQName QN_GOOGLE_MUC_USER_TYPE;
+extern const StaticQName QN_GOOGLE_MUC_USER_SRC_ID;
+extern const StaticQName QN_GOOGLE_MUC_USER_STATUS;
+extern const StaticQName 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;
+extern const char NS_GOOGLE_MUC_MEDIA[];
+extern const StaticQName QN_GOOGLE_MUC_AUDIO_MUTE;
+extern const StaticQName QN_GOOGLE_MUC_VIDEO_MUTE;
+extern const StaticQName QN_GOOGLE_MUC_RECORDING;
+extern const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK;
+extern const StaticQName QN_STATE_ATTR;
 
 }  // namespace buzz
 
diff --git a/talk/xmpp/fakexmppclient.h b/talk/xmpp/fakexmppclient.h
new file mode 100644
index 0000000..83b8e82
--- /dev/null
+++ b/talk/xmpp/fakexmppclient.h
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+// A fake XmppClient for use in unit tests.
+
+#ifndef TALK_XMPP_FAKEXMPPCLIENT_H_
+#define TALK_XMPP_FAKEXMPPCLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class XmlElement;
+
+class FakeXmppClient : public XmppTaskParentInterface,
+                       public XmppClientInterface {
+ public:
+  explicit FakeXmppClient(talk_base::TaskParent* parent)
+      : XmppTaskParentInterface(parent) {
+  }
+
+  // As XmppTaskParentInterface
+  virtual XmppClientInterface* GetClient() {
+    return this;
+  }
+
+  virtual int ProcessStart() {
+    return STATE_RESPONSE;
+  }
+
+  // As XmppClientInterface
+  virtual XmppEngine::State GetState() const {
+    return XmppEngine::STATE_OPEN;
+  }
+
+  virtual const Jid& jid() const {
+    return jid_;
+  }
+
+  virtual std::string NextId() {
+    // Implement if needed for tests.
+    return "0";
+  }
+
+  virtual XmppReturnStatus SendStanza(const XmlElement* stanza) {
+    sent_stanzas_.push_back(stanza);
+    return XMPP_RETURN_OK;
+  }
+
+  const std::vector<const XmlElement*>& sent_stanzas() {
+    return sent_stanzas_;
+  }
+
+  virtual XmppReturnStatus SendStanzaError(
+      const XmlElement * pelOriginal,
+      XmppStanzaError code,
+      const std::string & text) {
+    // Implement if needed for tests.
+    return XMPP_RETURN_OK;
+  }
+
+  virtual void AddXmppTask(XmppTask* task,
+                           XmppEngine::HandlerLevel level) {
+    tasks_.push_back(task);
+  }
+
+  virtual void RemoveXmppTask(XmppTask* task) {
+    std::remove(tasks_.begin(), tasks_.end(), task);
+  }
+
+  // As FakeXmppClient
+  void set_jid(const Jid& jid) {
+    jid_ = jid;
+  }
+
+  // Takes ownership of stanza.
+  void HandleStanza(XmlElement* stanza) {
+    for (std::vector<XmppTask*>::iterator task = tasks_.begin();
+         task != tasks_.end(); ++task) {
+      if ((*task)->HandleStanza(stanza)) {
+        delete stanza;
+        return;
+      }
+    }
+    delete stanza;
+  }
+
+ private:
+  Jid jid_;
+  std::vector<XmppTask*> tasks_;
+  std::vector<const XmlElement*> sent_stanzas_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_FAKEXMPPCLIENT_H_
diff --git a/talk/xmpp/hangoutpubsubclient.cc b/talk/xmpp/hangoutpubsubclient.cc
index ea5534d..8db2d45 100644
--- a/talk/xmpp/hangoutpubsubclient.cc
+++ b/talk/xmpp/hangoutpubsubclient.cc
@@ -40,8 +40,18 @@
 namespace buzz {
 
 namespace {
-const std::string kPresenting = "s";
-const std::string kNotPresenting = "o";
+const char kPresenting[] = "s";
+const char kNotPresenting[] = "o";
+const char kEmpty[] = "";
+
+const std::string GetPublisherNickFromPubSubItem(const XmlElement* item_elem) {
+  if (item_elem == NULL) {
+    return "";
+  }
+
+  return Jid(item_elem->Attr(QN_ATTR_PUBLISHER)).resource();
+}
+
 }  // namespace
 
 
@@ -49,10 +59,39 @@
 template <typename C>
 class PubSubStateSerializer {
  public:
+  virtual ~PubSubStateSerializer() {}
   virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
   virtual C Parse(const XmlElement* state_elem) = 0;
 };
 
+// Knows how to create "keys" for states, which determines their
+// uniqueness.  Most states are per-nick, but block is
+// per-blocker-and-blockee.  This is independent of itemid, especially
+// in the case of presenter state.
+class PubSubStateKeySerializer {
+ public:
+  virtual ~PubSubStateKeySerializer() {}
+  virtual std::string GetKey(const std::string& publisher_nick,
+                             const std::string& published_nick) = 0;
+};
+
+class PublishedNickKeySerializer : public PubSubStateKeySerializer {
+ public:
+  virtual std::string GetKey(const std::string& publisher_nick,
+                             const std::string& published_nick) {
+    return published_nick;
+  }
+};
+
+class PublisherAndPublishedNicksKeySerializer
+    : public PubSubStateKeySerializer {
+ public:
+  virtual std::string GetKey(const std::string& publisher_nick,
+                             const std::string& published_nick) {
+    return publisher_nick + ":" + published_nick;
+  }
+};
+
 // A simple serialiazer where presence of item => true, lack of item
 // => false.
 class BoolStateSerializer : public PubSubStateSerializer<bool> {
@@ -77,15 +116,19 @@
 template <typename C>
 class PubSubStateClient : public sigslot::has_slots<> {
  public:
-  // Gets ownership of the serializer, but not the client.
-  PubSubStateClient(PubSubClient* client,
+  // Gets ownership of the serializers, but not the client.
+  PubSubStateClient(const std::string& publisher_nick,
+                    PubSubClient* client,
                     const QName& state_name,
                     C default_state,
-                    PubSubStateSerializer<C>* serializer)
-      : client_(client),
+                    PubSubStateKeySerializer* key_serializer,
+                    PubSubStateSerializer<C>* state_serializer)
+      : publisher_nick_(publisher_nick),
+        client_(client),
         state_name_(state_name),
         default_state_(default_state) {
-    serializer_.reset(serializer);
+    key_serializer_.reset(key_serializer);
+    state_serializer_.reset(state_serializer);
     client_->SignalItems.connect(
         this, &PubSubStateClient<C>::OnItems);
     client_->SignalPublishResult.connect(
@@ -100,16 +143,16 @@
 
   virtual ~PubSubStateClient() {}
 
-  virtual void Publish(const std::string& key, const C& state,
+  virtual void Publish(const std::string& published_nick,
+                       const C& state,
                        std::string* task_id_out) {
-    const std::string& nick = key;
-
-    std::string itemid = state_name_.LocalPart() + ":" + nick;
+    std::string key = key_serializer_->GetKey(publisher_nick_, published_nick);
+    std::string itemid = state_name_.LocalPart() + ":" + key;
     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);
+      XmlElement* state_elem = state_serializer_->Write(state_name_, state);
+      state_elem->AddAttr(QN_NICK, published_nick);
       client_->PublishItem(itemid, state_elem, task_id_out);
     }
   };
@@ -124,17 +167,18 @@
                    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) {
+  // return false if retracted item (no info or state given)
+  virtual bool ParseStateItem(const PubSubItem& item,
+                              StateItemInfo* info_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);
+    info_out->publisher_nick = GetPublisherNickFromPubSubItem(item.elem);
+    info_out->published_nick = state_elem->Attr(QN_NICK);
+    *state_out = state_serializer_->Parse(state_elem);
     return true;
   };
 
@@ -155,27 +199,30 @@
 
   void OnItem(const PubSubItem& item) {
     const std::string& itemid = item.itemid;
-
-    std::string key;
+    StateItemInfo info;
     C new_state;
-    bool retracted = !ParseState(item, &key, &new_state);
+
+    bool retracted = !ParseStateItem(item, &info, &new_state);
     if (retracted) {
-      bool known_itemid = (key_by_itemid_.find(itemid) != key_by_itemid_.end());
+      bool known_itemid =
+          (info_by_itemid_.find(itemid) != info_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);
+        info = info_by_itemid_[itemid];
+        info_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;
+      // TODO: Assert new key matches the known key. It
+      // shouldn't change!
+      info_by_itemid_[itemid] = info;
     }
 
+    std::string key = key_serializer_->GetKey(
+        info.publisher_nick, info.published_nick);
     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)) {
@@ -191,11 +238,10 @@
     }
 
     PubSubStateChange<C> change;
-    change.key = key;
+    change.publisher_nick = info.publisher_nick;
+    change.published_nick = info.published_nick;
     change.old_state = old_state;
     change.new_state = new_state;
-    change.publisher_nick =
-        Jid(item.elem->Attr(QN_ATTR_PUBLISHER)).resource();
     SignalStateChange(change);
  }
 
@@ -231,33 +277,36 @@
     SignalPublishError(task_id, item, stanza);
   }
 
+  std::string publisher_nick_;
   PubSubClient* client_;
-  const QName& state_name_;
+  const QName state_name_;
   C default_state_;
-  talk_base::scoped_ptr<PubSubStateSerializer<C> > serializer_;
+  talk_base::scoped_ptr<PubSubStateKeySerializer> key_serializer_;
+  talk_base::scoped_ptr<PubSubStateSerializer<C> > state_serializer_;
   // key => state
   std::map<std::string, C> state_by_key_;
-  // itemid => key
-  std::map<std::string, std::string> key_by_itemid_;
+  // itemid => StateItemInfo
+  std::map<std::string, StateItemInfo> info_by_itemid_;
 };
 
 class PresenterStateClient : public PubSubStateClient<bool> {
  public:
-  PresenterStateClient(PubSubClient* client,
+  PresenterStateClient(const std::string& publisher_nick,
+                       PubSubClient* client,
                        const QName& state_name,
-                       bool default_state,
-                       PubSubStateSerializer<bool>* serializer = NULL)
-      : PubSubStateClient<bool>(client, state_name, default_state, serializer) {
+                       bool default_state)
+      : PubSubStateClient<bool>(
+          publisher_nick, client, state_name, default_state,
+          new PublishedNickKeySerializer(), NULL) {
   }
 
-  virtual void Publish(const std::string& key, const bool& state,
+  virtual void Publish(const std::string& published_nick,
+                       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);
+    presenter_elem->AddAttr(QN_NICK, published_nick);
 
     XmlElement* presentation_item_elem =
         new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false);
@@ -267,7 +316,7 @@
 
     // 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::string itemid = published_nick;
     std::vector<XmlElement*> children;
     children.push_back(presenter_elem);
     children.push_back(presentation_item_elem);
@@ -275,9 +324,9 @@
   }
 
  protected:
-  virtual bool ParseState(const PubSubItem& item,
-                          std::string* key_out,
-                          bool* state_out) {
+  virtual bool ParseStateItem(const PubSubItem& item,
+                              StateItemInfo* info_out,
+                              bool* state_out) {
     const XmlElement* presenter_elem =
         item.elem->FirstNamed(QN_PRESENTER_PRESENTER);
     const XmlElement* presentation_item_elem =
@@ -286,11 +335,16 @@
       return false;
     }
 
-    *key_out = presenter_elem->Attr(QN_NICK);
+    info_out->publisher_nick = GetPublisherNickFromPubSubItem(item.elem);
+    info_out->published_nick = presenter_elem->Attr(QN_NICK);
     *state_out = (presentation_item_elem->Attr(
         QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
     return true;
   }
+
+  virtual bool StatesEqual(bool state1, bool state2) {
+    return false;  // Make every item trigger an event, even if state doesn't change.
+  }
 };
 
 HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent,
@@ -307,7 +361,7 @@
       this, &HangoutPubSubClient::OnMediaRequestError);
 
   presenter_state_client_.reset(new PresenterStateClient(
-      presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
+      nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
   presenter_state_client_->SignalStateChange.connect(
       this, &HangoutPubSubClient::OnPresenterStateChange);
   presenter_state_client_->SignalPublishResult.connect(
@@ -316,8 +370,8 @@
       this, &HangoutPubSubClient::OnPresenterPublishError);
 
   audio_mute_state_client_.reset(new PubSubStateClient<bool>(
-      media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
-      new BoolStateSerializer()));
+      nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
+      new PublishedNickKeySerializer(), new BoolStateSerializer()));
   // Can't just repeat because we need to watch for remote mutes.
   audio_mute_state_client_->SignalStateChange.connect(
       this, &HangoutPubSubClient::OnAudioMuteStateChange);
@@ -327,14 +381,25 @@
       this, &HangoutPubSubClient::OnAudioMutePublishError);
 
   recording_state_client_.reset(new PubSubStateClient<bool>(
-      media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
-      new BoolStateSerializer()));
+      nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
+      new PublishedNickKeySerializer(), 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);
+
+  media_block_state_client_.reset(new PubSubStateClient<bool>(
+      nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false,
+      new PublisherAndPublishedNicksKeySerializer(),
+      new BoolStateSerializer()));
+  media_block_state_client_->SignalStateChange.connect(
+      this, &HangoutPubSubClient::OnMediaBlockStateChange);
+  media_block_state_client_->SignalPublishResult.connect(
+      this, &HangoutPubSubClient::OnMediaBlockPublishResult);
+  media_block_state_client_->SignalPublishError.connect(
+      this, &HangoutPubSubClient::OnMediaBlockPublishError);
 }
 
 HangoutPubSubClient::~HangoutPubSubClient() {
@@ -376,10 +441,17 @@
   audio_mute_state_client_->Publish(mutee_nick, true, task_id_out);
 }
 
+// Block media is accomplished by setting another client's block
+// state, kind of like remote mute.
+void HangoutPubSubClient::BlockMedia(
+    const std::string& blockee_nick, std::string* task_id_out) {
+  media_block_state_client_->Publish(blockee_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);
+  SignalPresenterStateChange(
+      change.published_nick, change.old_state, change.new_state);
 }
 
 void HangoutPubSubClient::OnPresenterPublishResult(
@@ -398,23 +470,21 @@
 // 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);
+  bool remote_action = (!change.publisher_nick.empty() &&
+                        (change.publisher_nick != change.published_nick));
   if (is_muted && remote_action) {
-    const std::string& mutee_nick = nick;
+    const std::string& mutee_nick = change.published_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);
+    SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted);
   }
 }
 
-const std::string& GetAudioMuteNickFromItem(const XmlElement* item) {
+const std::string GetAudioMuteNickFromItem(const XmlElement* item) {
   if (item != NULL) {
     const XmlElement* audio_mute_state =
         item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE);
@@ -422,7 +492,18 @@
       return audio_mute_state->Attr(QN_NICK);
     }
   }
-  return STR_EMPTY;
+  return std::string();
+}
+
+const std::string GetBlockeeNickFromItem(const XmlElement* item) {
+  if (item != NULL) {
+    const XmlElement* media_block_state =
+        item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK);
+    if (media_block_state != NULL) {
+      return media_block_state->Attr(QN_NICK);
+    }
+  }
+  return std::string();
 }
 
 void HangoutPubSubClient::OnAudioMutePublishResult(
@@ -448,8 +529,8 @@
 
 void HangoutPubSubClient::OnRecordingStateChange(
     const PubSubStateChange<bool>& change) {
-  const std::string& nick = change.key;
-  SignalRecordingStateChange(nick, change.old_state, change.new_state);
+  SignalRecordingStateChange(
+      change.published_nick, change.old_state, change.new_state);
 }
 
 void HangoutPubSubClient::OnRecordingPublishResult(
@@ -463,4 +544,32 @@
   SignalPublishRecordingError(task_id, stanza);
 }
 
+void HangoutPubSubClient::OnMediaBlockStateChange(
+    const PubSubStateChange<bool>& change) {
+  const std::string& blockee_nick = change.published_nick;
+  const std::string& blocker_nick = change.publisher_nick;
+
+  bool was_blockee = change.old_state;
+  bool is_blockee = change.new_state;
+  if (!was_blockee && is_blockee) {
+    SignalMediaBlock(blockee_nick, blocker_nick);
+  }
+  // TODO: Should we bother signaling unblock? Currently
+  // it isn't allowed, but it might happen when a participant leaves
+  // the room and the item is retracted.
+}
+
+void HangoutPubSubClient::OnMediaBlockPublishResult(
+    const std::string& task_id, const XmlElement* item) {
+  const std::string& blockee_nick = GetBlockeeNickFromItem(item);
+  SignalMediaBlockResult(task_id, blockee_nick);
+}
+
+void HangoutPubSubClient::OnMediaBlockPublishError(
+    const std::string& task_id, const XmlElement* item,
+    const XmlElement* stanza) {
+  const std::string& blockee_nick = GetBlockeeNickFromItem(item);
+  SignalMediaBlockError(task_id, blockee_nick, stanza);
+}
+
 }  // namespace buzz
diff --git a/talk/xmpp/hangoutpubsubclient.h b/talk/xmpp/hangoutpubsubclient.h
index e740738..ae9dc57 100644
--- a/talk/xmpp/hangoutpubsubclient.h
+++ b/talk/xmpp/hangoutpubsubclient.h
@@ -47,14 +47,26 @@
 class XmlElement;
 class XmppTaskParentInterface;
 
+// To handle retracts correctly, we need to remember certain details
+// about an item.  We could just cache the entire XML element, but
+// that would take more memory and require re-parsing.
+struct StateItemInfo {
+  std::string published_nick;
+  std::string publisher_nick;
+};
+
 // Represents a PubSub state change.  Usually, the key is the nick,
-// but not always.  It's a per-state-type thing.
+// but not always.  It's a per-state-type thing.  Currently documented
+// at https://docs.google.com/a/google.com/document/d/
+// 1QyHu_ufyVdf0VICdfc_DtJbrOdrdIUm4eM73RZqnivI/edit?hl=en_US
 template <typename C>
 struct PubSubStateChange {
-  std::string key;
+  // The nick of the user changing the state.
+  std::string publisher_nick;
+  // The nick of the user whose state is changing.
+  std::string published_nick;
   C old_state;
   C new_state;
-  std::string publisher_nick;
 };
 
 template <typename C> class PubSubStateClient;
@@ -80,10 +92,12 @@
   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)
+  // Signal (mutee_nick, muter_nick, should_mute_locally)
   sigslot::signal3<const std::string&,
                    const std::string&,
                    bool> SignalRemoteMute;
+  // Signal (blockee_nick, blocker_nick)
+  sigslot::signal2<const std::string&, const std::string&> SignalMediaBlock;
 
   // Signal (node, error stanza)
   sigslot::signal2<const std::string&, const XmlElement*> SignalRequestError;
@@ -98,6 +112,8 @@
       bool recording, std::string* task_id_out = NULL);
   void RemoteMute(
       const std::string& mutee_nick, std::string* task_id_out = NULL);
+  void BlockMedia(
+      const std::string& blockee_nick, std::string* task_id_out = NULL);
 
   // Signal task_id
   sigslot::signal1<const std::string&> SignalPublishAudioMuteResult;
@@ -106,6 +122,9 @@
   // Signal (task_id, mutee_nick)
   sigslot::signal2<const std::string&,
                    const std::string&> SignalRemoteMuteResult;
+  // Signal (task_id, blockee_nick)
+  sigslot::signal2<const std::string&,
+                   const std::string&> SignalMediaBlockResult;
 
   // Signal (task_id, error stanza)
   sigslot::signal2<const std::string&,
@@ -114,10 +133,17 @@
                    const XmlElement*> SignalPublishPresenterError;
   sigslot::signal2<const std::string&,
                    const XmlElement*> SignalPublishRecordingError;
+  sigslot::signal2<const std::string&,
+                   const XmlElement*> SignalPublishMediaBlockError;
   // Signal (task_id, mutee_nick, error stanza)
   sigslot::signal3<const std::string&,
                    const std::string&,
                    const XmlElement*> SignalRemoteMuteError;
+  // Signal (task_id, blockee_nick, error stanza)
+  sigslot::signal3<const std::string&,
+                   const std::string&,
+                   const XmlElement*> SignalMediaBlockError;
+
 
  private:
   void OnPresenterRequestError(PubSubClient* client,
@@ -143,6 +169,12 @@
   void OnRecordingPublishError(const std::string& task_id,
                                const XmlElement* item,
                                const XmlElement* stanza);
+  void OnMediaBlockStateChange(const PubSubStateChange<bool>& change);
+  void OnMediaBlockPublishResult(const std::string& task_id,
+                                 const XmlElement* item);
+  void OnMediaBlockPublishError(const std::string& task_id,
+                                const XmlElement* item,
+                                const XmlElement* stanza);
   Jid mucjid_;
   std::string nick_;
   talk_base::scoped_ptr<PubSubClient> media_client_;
@@ -150,6 +182,7 @@
   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_;
+  talk_base::scoped_ptr<PubSubStateClient<bool> > media_block_state_client_;
 };
 
 }  // namespace buzz
diff --git a/talk/xmpp/hangoutpubsubclient_unittest.cc b/talk/xmpp/hangoutpubsubclient_unittest.cc
new file mode 100644
index 0000000..a0c2439
--- /dev/null
+++ b/talk/xmpp/hangoutpubsubclient_unittest.cc
@@ -0,0 +1,625 @@
+// Copyright 2011 Google Inc. All Rights Reserved
+
+
+#include <string>
+
+#include "talk/base/faketaskrunner.h"
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/fakexmppclient.h"
+#include "talk/xmpp/hangoutpubsubclient.h"
+
+class TestHangoutPubSubListener : public sigslot::has_slots<> {
+ public:
+  TestHangoutPubSubListener() :
+      request_error_count(0),
+      publish_audio_mute_error_count(0),
+      publish_presenter_error_count(0),
+      publish_recording_error_count(0),
+      remote_mute_error_count(0) {
+  }
+
+  void OnPresenterStateChange(
+      const std::string& nick, bool was_presenting, bool is_presenting) {
+    last_presenter_nick = nick;
+    last_was_presenting = was_presenting;
+    last_is_presenting = is_presenting;
+  }
+
+  void OnAudioMuteStateChange(
+      const std::string& nick, bool was_muted, bool is_muted) {
+    last_audio_muted_nick = nick;
+    last_was_audio_muted = was_muted;
+    last_is_audio_muted = is_muted;
+  }
+
+  void OnRecordingStateChange(
+      const std::string& nick, bool was_recording, bool is_recording) {
+    last_recording_nick = nick;
+    last_was_recording = was_recording;
+    last_is_recording = is_recording;
+  }
+
+  void OnRemoteMute(
+      const std::string& mutee_nick,
+      const std::string& muter_nick,
+      bool should_mute_locally) {
+    last_mutee_nick = mutee_nick;
+    last_muter_nick = muter_nick;
+    last_should_mute = should_mute_locally;
+  }
+
+  void OnMediaBlock(
+      const std::string& blockee_nick,
+      const std::string& blocker_nick) {
+    last_blockee_nick = blockee_nick;
+    last_blocker_nick = blocker_nick;
+  }
+
+  void OnRequestError(const std::string& node, const buzz::XmlElement* stanza) {
+    ++request_error_count;
+    request_error_node = node;
+  }
+
+  void OnPublishAudioMuteError(const std::string& task_id,
+                               const buzz::XmlElement* stanza) {
+    ++publish_audio_mute_error_count;
+    error_task_id = task_id;
+  }
+
+  void OnPublishPresenterError(const std::string& task_id,
+                               const buzz::XmlElement* stanza) {
+    ++publish_presenter_error_count;
+    error_task_id = task_id;
+  }
+
+  void OnPublishRecordingError(const std::string& task_id,
+                               const buzz::XmlElement* stanza) {
+    ++publish_recording_error_count;
+    error_task_id = task_id;
+  }
+
+  void OnRemoteMuteResult(const std::string& task_id,
+                          const std::string& mutee_nick) {
+    result_task_id = task_id;
+    remote_mute_mutee_nick = mutee_nick;
+  }
+
+  void OnRemoteMuteError(const std::string& task_id,
+                         const std::string& mutee_nick,
+                         const buzz::XmlElement* stanza) {
+    ++remote_mute_error_count;
+    error_task_id = task_id;
+    remote_mute_mutee_nick = mutee_nick;
+  }
+
+  void OnMediaBlockResult(const std::string& task_id,
+                          const std::string& blockee_nick) {
+    result_task_id = task_id;
+    media_blockee_nick = blockee_nick;
+  }
+
+  void OnMediaBlockError(const std::string& task_id,
+                         const std::string& blockee_nick,
+                         const buzz::XmlElement* stanza) {
+    ++media_block_error_count;
+    error_task_id = task_id;
+    media_blockee_nick = blockee_nick;
+  }
+
+  std::string last_presenter_nick;
+  bool last_is_presenting;
+  bool last_was_presenting;
+  std::string last_audio_muted_nick;
+  bool last_is_audio_muted;
+  bool last_was_audio_muted;
+  std::string last_recording_nick;
+  bool last_is_recording;
+  bool last_was_recording;
+  std::string last_mutee_nick;
+  std::string last_muter_nick;
+  bool last_should_mute;
+  std::string last_blockee_nick;
+  std::string last_blocker_nick;
+
+  int request_error_count;
+  std::string request_error_node;
+  int publish_audio_mute_error_count;
+  int publish_presenter_error_count;
+  int publish_recording_error_count;
+  int remote_mute_error_count;
+  std::string result_task_id;
+  std::string error_task_id;
+  std::string remote_mute_mutee_nick;
+  int media_block_error_count;
+  std::string media_blockee_nick;
+};
+
+class HangoutPubSubClientTest : public testing::Test {
+ public:
+  HangoutPubSubClientTest() :
+      pubsubjid("room@domain.com"),
+      nick("me") {
+
+    runner.reset(new talk_base::FakeTaskRunner());
+    xmpp_client = new buzz::FakeXmppClient(runner.get());
+    client.reset(new buzz::HangoutPubSubClient(xmpp_client, pubsubjid, nick));
+    listener.reset(new TestHangoutPubSubListener());
+    client->SignalPresenterStateChange.connect(
+        listener.get(), &TestHangoutPubSubListener::OnPresenterStateChange);
+    client->SignalAudioMuteStateChange.connect(
+        listener.get(), &TestHangoutPubSubListener::OnAudioMuteStateChange);
+    client->SignalRecordingStateChange.connect(
+        listener.get(), &TestHangoutPubSubListener::OnRecordingStateChange);
+    client->SignalRemoteMute.connect(
+        listener.get(), &TestHangoutPubSubListener::OnRemoteMute);
+    client->SignalMediaBlock.connect(
+        listener.get(), &TestHangoutPubSubListener::OnMediaBlock);
+    client->SignalRequestError.connect(
+        listener.get(), &TestHangoutPubSubListener::OnRequestError);
+    client->SignalPublishAudioMuteError.connect(
+        listener.get(), &TestHangoutPubSubListener::OnPublishAudioMuteError);
+    client->SignalPublishPresenterError.connect(
+        listener.get(), &TestHangoutPubSubListener::OnPublishPresenterError);
+    client->SignalPublishRecordingError.connect(
+        listener.get(), &TestHangoutPubSubListener::OnPublishRecordingError);
+    client->SignalRemoteMuteResult.connect(
+        listener.get(), &TestHangoutPubSubListener::OnRemoteMuteResult);
+    client->SignalRemoteMuteError.connect(
+        listener.get(), &TestHangoutPubSubListener::OnRemoteMuteError);
+    client->SignalMediaBlockResult.connect(
+        listener.get(), &TestHangoutPubSubListener::OnMediaBlockResult);
+    client->SignalMediaBlockError.connect(
+        listener.get(), &TestHangoutPubSubListener::OnMediaBlockError);
+  }
+
+  talk_base::scoped_ptr<talk_base::FakeTaskRunner> runner;
+  // xmpp_client deleted by deleting runner.
+  buzz::FakeXmppClient* xmpp_client;
+  talk_base::scoped_ptr<buzz::HangoutPubSubClient> client;
+  talk_base::scoped_ptr<TestHangoutPubSubListener> listener;
+  buzz::Jid pubsubjid;
+  std::string nick;
+};
+
+TEST_F(HangoutPubSubClientTest, TestRequest) {
+  ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+  client->RequestAll();
+  std::string expected_presenter_request =
+      "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+          "<pub:items node=\"google:presenter\"/>"
+        "</pub:pubsub>"
+      "</cli:iq>";
+
+  std::string expected_media_request =
+      "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+          "<pub:items node=\"google:muc#media\"/>"
+        "</pub:pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(2U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_presenter_request, xmpp_client->sent_stanzas()[0]->Str());
+  EXPECT_EQ(expected_media_request, xmpp_client->sent_stanzas()[1]->Str());
+
+  std::string presenter_response =
+      "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+      "  <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+      "    <items node='google:presenter'>"
+      "      <item id='12345'>"
+      "        <presenter xmlns='google:presenter' nick='presenting-nick'/>"
+      "        <pre:presentation-item xmlns:pre='google:presenter'"
+      "          pre:presentation-type='o'/>"
+      "      </item>"
+      // Some clients are "bad" in that they'll jam multiple states in
+      // all at once.  We have to deal with it.
+      "      <item id='12346'>"
+      "        <presenter xmlns='google:presenter' nick='presenting-nick'/>"
+      "        <pre:presentation-item xmlns:pre='google:presenter'"
+      "          pre:presentation-type='s'/>"
+      "      </item>"
+      "      <item id='12347'>"
+      "        <presenter xmlns='google:presenter' nick='presenting-nick2'/>"
+      "        <pre:presentation-item xmlns:pre='google:presenter'"
+      "          pre:presentation-type='s'/>"
+      "      </item>"
+      "    </items>"
+      "  </pubsub>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(presenter_response));
+  EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+  EXPECT_FALSE(listener->last_was_presenting);
+  EXPECT_TRUE(listener->last_is_presenting);
+
+  std::string media_response =
+      "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+      "  <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+      "    <items node='google:muc#media'>"
+      "      <item id='audio-mute:muted-nick'>"
+      "        <audio-mute nick='muted-nick' xmlns='google:muc#media'/>"
+      "      </item>"
+      "      <item id='recording:recording-nick'>"
+      "        <recording nick='recording-nick' xmlns='google:muc#media'/>"
+      "      </item>"
+      "    </items>"
+      "  </pubsub>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(media_response));
+  EXPECT_EQ("muted-nick", listener->last_audio_muted_nick);
+  EXPECT_FALSE(listener->last_was_audio_muted);
+  EXPECT_TRUE(listener->last_is_audio_muted);
+  EXPECT_EQ("recording-nick", listener->last_recording_nick);
+  EXPECT_FALSE(listener->last_was_recording);
+  EXPECT_TRUE(listener->last_is_recording);
+
+  std::string incoming_presenter_resets_message =
+      "<message xmlns='jabber:client' from='room@domain.com'>"
+      "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+      "    <items node='google:presenter'>"
+      "      <item id='12348'>"
+      "        <presenter xmlns='google:presenter' nick='presenting-nick'/>"
+      "        <pre:presentation-item xmlns:pre='google:presenter'"
+      "          pre:presentation-type='o'/>"
+      "      </item>"
+      "    </items>"
+      "  </event>"
+      "</message>";
+
+  xmpp_client->HandleStanza(
+      buzz::XmlElement::ForStr(incoming_presenter_resets_message));
+  EXPECT_EQ("presenting-nick", listener->last_presenter_nick);
+  //EXPECT_TRUE(listener->last_was_presenting);
+  EXPECT_FALSE(listener->last_is_presenting);
+
+  std::string incoming_presenter_retracts_message =
+      "<message xmlns='jabber:client' from='room@domain.com'>"
+      "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+      "    <items node='google:presenter'>"
+      "      <retract id='12347'/>"
+      "    </items>"
+      "  </event>"
+      "</message>";
+
+  xmpp_client->HandleStanza(
+      buzz::XmlElement::ForStr(incoming_presenter_retracts_message));
+  EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+  EXPECT_TRUE(listener->last_was_presenting);
+  EXPECT_FALSE(listener->last_is_presenting);
+
+  std::string incoming_media_retracts_message =
+      "<message xmlns='jabber:client' from='room@domain.com'>"
+      "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+      "    <items node='google:muc#media'>"
+      "      <item id='audio-mute:muted-nick'>"
+      "      </item>"
+      "      <retract id='recording:recording-nick'/>"
+      "    </items>"
+      "  </event>"
+      "</message>";
+
+  xmpp_client->HandleStanza(
+      buzz::XmlElement::ForStr(incoming_media_retracts_message));
+  EXPECT_EQ("muted-nick", listener->last_audio_muted_nick);
+  EXPECT_TRUE(listener->last_was_audio_muted);
+  EXPECT_FALSE(listener->last_is_audio_muted);
+  EXPECT_EQ("recording-nick", listener->last_recording_nick);
+  EXPECT_TRUE(listener->last_was_recording);
+  EXPECT_FALSE(listener->last_is_recording);
+
+  std::string incoming_presenter_changes_message =
+      "<message xmlns='jabber:client' from='room@domain.com'>"
+      "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+      "    <items node='google:presenter'>"
+      "      <item id='presenting-nick2'>"
+      "        <presenter xmlns='google:presenter' nick='presenting-nick2'/>"
+      "        <pre:presentation-item xmlns:pre='google:presenter'"
+      "          pre:presentation-type='s'/>"
+      "      </item>"
+      "    </items>"
+      "  </event>"
+      "</message>";
+
+  xmpp_client->HandleStanza(
+      buzz::XmlElement::ForStr(incoming_presenter_changes_message));
+  EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+  EXPECT_FALSE(listener->last_was_presenting);
+  EXPECT_TRUE(listener->last_is_presenting);
+
+  xmpp_client->HandleStanza(
+      buzz::XmlElement::ForStr(incoming_presenter_changes_message));
+  EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+  EXPECT_TRUE(listener->last_was_presenting);
+  EXPECT_TRUE(listener->last_is_presenting);
+
+  std::string incoming_media_changes_message =
+      "<message xmlns='jabber:client' from='room@domain.com'>"
+      "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+      "    <items node='google:muc#media'>"
+      "      <item id='audio-mute:muted-nick2'>"
+      "        <audio-mute nick='muted-nick2' xmlns='google:muc#media'/>"
+      "      </item>"
+      "      <item id='recording:recording-nick2'>"
+      "        <recording nick='recording-nick2' xmlns='google:muc#media'/>"
+      "      </item>"
+      "    </items>"
+      "  </event>"
+      "</message>";
+
+  xmpp_client->HandleStanza(
+      buzz::XmlElement::ForStr(incoming_media_changes_message));
+  EXPECT_EQ("muted-nick2", listener->last_audio_muted_nick);
+  EXPECT_FALSE(listener->last_was_audio_muted);
+  EXPECT_TRUE(listener->last_is_audio_muted);
+  EXPECT_EQ("recording-nick2", listener->last_recording_nick);
+  EXPECT_FALSE(listener->last_was_recording);
+  EXPECT_TRUE(listener->last_is_recording);
+
+  std::string incoming_remote_mute_message =
+      "<message xmlns='jabber:client' from='room@domain.com'>"
+      "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+      "    <items node='google:muc#media'>"
+      "      <item id='audio-mute:mutee' publisher='room@domain.com/muter'>"
+      "        <audio-mute nick='mutee' xmlns='google:muc#media'/>"
+      "      </item>"
+      "    </items>"
+      "  </event>"
+      "</message>";
+
+  xmpp_client->HandleStanza(
+      buzz::XmlElement::ForStr(incoming_remote_mute_message));
+  EXPECT_EQ("mutee", listener->last_mutee_nick);
+  EXPECT_EQ("muter", listener->last_muter_nick);
+  EXPECT_FALSE(listener->last_should_mute);
+
+  std::string incoming_remote_mute_me_message =
+      "<message xmlns='jabber:client' from='room@domain.com'>"
+      "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+      "    <items node='google:muc#media'>"
+      "      <item id='audio-mute:me' publisher='room@domain.com/muter'>"
+      "        <audio-mute nick='me' xmlns='google:muc#media'/>"
+      "      </item>"
+      "    </items>"
+      "  </event>"
+      "</message>";
+
+  xmpp_client->HandleStanza(
+      buzz::XmlElement::ForStr(incoming_remote_mute_me_message));
+  EXPECT_EQ("me", listener->last_mutee_nick);
+  EXPECT_EQ("muter", listener->last_muter_nick);
+  EXPECT_TRUE(listener->last_should_mute);
+
+  std::string incoming_media_block_message =
+      "<message xmlns='jabber:client' from='room@domain.com'>"
+      "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+      "    <items node='google:muc#media'>"
+      "      <item id='block:blocker:blockee'"
+      "            publisher='room@domain.com/blocker'>"
+      "        <block nick='blockee' xmlns='google:muc#media'/>"
+      "      </item>"
+      "    </items>"
+      "  </event>"
+      "</message>";
+
+  xmpp_client->HandleStanza(
+      buzz::XmlElement::ForStr(incoming_media_block_message));
+  EXPECT_EQ("blockee", listener->last_blockee_nick);
+  EXPECT_EQ("blocker", listener->last_blocker_nick);
+}
+
+TEST_F(HangoutPubSubClientTest, TestRequestError) {
+  client->RequestAll();
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+      "  <error type='auth'>"
+      "    <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+      "  </error>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(1, listener->request_error_count);
+  EXPECT_EQ("google:presenter", listener->request_error_node);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublish) {
+  client->PublishPresenterState(true);
+  std::string expected_presenter_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<publish node=\"google:presenter\">"
+            "<item id=\"me\">"
+              "<presenter xmlns=\"google:presenter\""
+              " jid=\"dummy@value.net\" nick=\"me\"/>"
+              "<pre:presentation-item"
+              " pre:presentation-type=\"s\" xmlns:pre=\"google:presenter\"/>"
+            "</item>"
+          "</publish>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_presenter_iq,
+            xmpp_client->sent_stanzas()[0]->Str());
+
+  client->PublishAudioMuteState(true);
+  std::string expected_audio_mute_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<publish node=\"google:muc#media\">"
+            "<item id=\"audio-mute:me\">"
+              "<audio-mute xmlns=\"google:muc#media\" nick=\"me\"/>"
+            "</item>"
+          "</publish>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(2U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_audio_mute_iq, xmpp_client->sent_stanzas()[1]->Str());
+
+  client->PublishRecordingState(true);
+  std::string expected_recording_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<publish node=\"google:muc#media\">"
+            "<item id=\"recording:me\">"
+              "<recording xmlns=\"google:muc#media\" nick=\"me\"/>"
+            "</item>"
+          "</publish>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(3U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_recording_iq, xmpp_client->sent_stanzas()[2]->Str());
+
+  client->RemoteMute("mutee");
+  std::string expected_remote_mute_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<publish node=\"google:muc#media\">"
+            "<item id=\"audio-mute:mutee\">"
+              "<audio-mute xmlns=\"google:muc#media\" nick=\"mutee\"/>"
+            "</item>"
+          "</publish>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(4U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_remote_mute_iq, xmpp_client->sent_stanzas()[3]->Str());
+
+  client->PublishPresenterState(false);
+  std::string expected_presenter_retract_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<publish node=\"google:presenter\">"
+            "<item id=\"me\">"
+              "<presenter xmlns=\"google:presenter\""
+              " jid=\"dummy@value.net\" nick=\"me\"/>"
+              "<pre:presentation-item"
+              " pre:presentation-type=\"o\" xmlns:pre=\"google:presenter\"/>"
+            "</item>"
+          "</publish>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(5U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_presenter_retract_iq,
+            xmpp_client->sent_stanzas()[4]->Str());
+
+  client->PublishAudioMuteState(false);
+  std::string expected_audio_mute_retract_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<retract node=\"google:muc#media\" notify=\"true\">"
+            "<item id=\"audio-mute:me\"/>"
+          "</retract>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(6U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_audio_mute_retract_iq,
+            xmpp_client->sent_stanzas()[5]->Str());
+
+  client->BlockMedia("blockee");
+  std::string expected_media_block_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<publish node=\"google:muc#media\">"
+            "<item id=\"block:me:blockee\">"
+              "<block xmlns=\"google:muc#media\" nick=\"blockee\"/>"
+            "</item>"
+          "</publish>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(7U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_media_block_iq, xmpp_client->sent_stanzas()[6]->Str());
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishPresenterError) {
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+  client->PublishPresenterState(true);
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(1, listener->publish_presenter_error_count);
+  EXPECT_EQ("0", listener->error_task_id);
+}
+
+
+TEST_F(HangoutPubSubClientTest, TestPublishAudioMuteError) {
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+  client->PublishAudioMuteState(true);
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(1, listener->publish_audio_mute_error_count);
+  EXPECT_EQ("0", listener->error_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishRecordingError) {
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+  client->PublishRecordingState(true);
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(1, listener->publish_recording_error_count);
+  EXPECT_EQ("0", listener->error_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishRemoteMuteResult) {
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+  client->RemoteMute("joe");
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ("joe", listener->remote_mute_mutee_nick);
+  EXPECT_EQ("0", listener->result_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestRemoteMuteError) {
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+  client->RemoteMute("joe");
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(1, listener->remote_mute_error_count);
+  EXPECT_EQ("joe", listener->remote_mute_mutee_nick);
+  EXPECT_EQ("0", listener->error_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishMediaBlockResult) {
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+  client->BlockMedia("joe");
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ("joe", listener->media_blockee_nick);
+  EXPECT_EQ("0", listener->result_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestMediaBlockError) {
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+  client->BlockMedia("joe");
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(1, listener->remote_mute_error_count);
+  EXPECT_EQ("joe", listener->media_blockee_nick);
+  EXPECT_EQ("0", listener->error_task_id);
+}
diff --git a/talk/xmpp/jid.cc b/talk/xmpp/jid.cc
index 01a025f..ae01e5d 100644
--- a/talk/xmpp/jid.cc
+++ b/talk/xmpp/jid.cc
@@ -2,26 +2,26 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
@@ -38,106 +38,59 @@
 
 namespace buzz {
 
-Jid::Jid() : data_(NULL) {
+Jid::Jid() {
 }
 
-Jid::Jid(bool is_special, const std::string & special) {
-  data_ = is_special ? new Data(special, STR_EMPTY, STR_EMPTY) : NULL;
-}
-
-Jid::Jid(const std::string & jid_string) {
-  if (jid_string == STR_EMPTY) {
-    data_ = NULL;
+Jid::Jid(const std::string& jid_string) {
+  if (jid_string.empty())
     return;
-  }
 
-  // First find the slash and slice of that part
+  // First find the slash and slice off that part
   size_t slash = jid_string.find('/');
-  std::string resource_name = (slash == std::string::npos ? STR_EMPTY :
+  resource_name_ = (slash == std::string::npos ? STR_EMPTY :
                     jid_string.substr(slash + 1));
 
   // Now look for the node
-  std::string node_name;
   size_t at = jid_string.find('@');
   size_t domain_begin;
   if (at < slash && at != std::string::npos) {
-    node_name = jid_string.substr(0, at);
+    node_name_ = jid_string.substr(0, at);
     domain_begin = at + 1;
   } else {
     domain_begin = 0;
   }
 
   // Now take what is left as the domain
-  size_t domain_length =
-    (  slash == std::string::npos
-     ? jid_string.length() - domain_begin
-     : slash - domain_begin);
+  size_t domain_length = (slash == std::string::npos) ?
+      (jid_string.length() - domain_begin) : (slash - domain_begin);
+  domain_name_ = jid_string.substr(domain_begin, domain_length);
 
-  // avoid allocating these constants repeatedly
-  std::string domain_name;
-
-  if (domain_length == 9  && jid_string.find("gmail.com", domain_begin) == domain_begin) {
-    domain_name = STR_GMAIL_COM;
-  }
-  else if (domain_length == 14 && jid_string.find("googlemail.com", domain_begin) == domain_begin) {
-    domain_name = STR_GOOGLEMAIL_COM;
-  }
-  else if (domain_length == 10 && jid_string.find("google.com", domain_begin) == domain_begin) {
-    domain_name = STR_GOOGLE_COM;
-  }
-  else {
-    domain_name = jid_string.substr(domain_begin, domain_length);
-  }
-
-  // If the domain is empty we have a non-valid jid and we should empty
-  // everything else out
-  if (domain_name.empty()) {
-    data_ = NULL;
-    return;
-  }
-  
-  bool valid_node;
-  std::string validated_node = prepNode(node_name, 
-      node_name.begin(), node_name.end(), &valid_node);
-  bool valid_domain;
-  std::string validated_domain = prepDomain(domain_name,
-      domain_name.begin(), domain_name.end(), &valid_domain);
-  bool valid_resource;
-  std::string validated_resource = prepResource(resource_name,
-      resource_name.begin(), resource_name.end(), &valid_resource);
-
-  if (!valid_node || !valid_domain || !valid_resource) {
-    data_ = NULL;
-    return;
-  }
-
-  data_ = new Data(validated_node, validated_domain, validated_resource);
+  ValidateOrReset();
 }
 
-Jid::Jid(const std::string & node_name,
-         const std::string & domain_name,
-         const std::string & resource_name) {
-  if (domain_name.empty()) {
-    data_ = NULL;
-    return;
-  }
+Jid::Jid(const std::string& node_name,
+         const std::string& domain_name,
+         const std::string& resource_name)
+    :  node_name_(node_name),
+       domain_name_(domain_name),
+       resource_name_(resource_name) {
+  ValidateOrReset();
+}
 
+void Jid::ValidateOrReset() {
   bool valid_node;
-  std::string validated_node = prepNode(node_name, 
-      node_name.begin(), node_name.end(), &valid_node);
   bool valid_domain;
-  std::string validated_domain = prepDomain(domain_name,
-      domain_name.begin(), domain_name.end(), &valid_domain);
   bool valid_resource;
-  std::string validated_resource = prepResource(resource_name,
-      resource_name.begin(), resource_name.end(), &valid_resource);
+
+  node_name_ = PrepNode(node_name_, &valid_node);
+  domain_name_ = PrepDomain(domain_name_, &valid_domain);
+  resource_name_ = PrepResource(resource_name_, &valid_resource);
 
   if (!valid_node || !valid_domain || !valid_resource) {
-    data_ = NULL;
-    return;
+    node_name_.clear();
+    domain_name_.clear();
+    resource_name_.clear();
   }
-
-  data_ = new Data(validated_node, validated_domain, validated_resource);
 }
 
 std::string Jid::Str() const {
@@ -146,143 +99,85 @@
 
   std::string ret;
 
-  if (!data_->node_name_.empty())
-    ret = data_->node_name_ + "@";
+  if (!node_name_.empty())
+    ret = node_name_ + "@";
 
-  ASSERT(data_->domain_name_ != STR_EMPTY);
-  ret += data_->domain_name_;
+  ASSERT(domain_name_ != STR_EMPTY);
+  ret += domain_name_;
 
-  if (!data_->resource_name_.empty())
-    ret += "/" + data_->resource_name_;
+  if (!resource_name_.empty())
+    ret += "/" + resource_name_;
 
   return ret;
 }
 
-bool
-Jid::IsValid() const {
-  return data_ != NULL && !data_->domain_name_.empty();
+Jid::~Jid() {
 }
 
-bool
-Jid::IsBare() const {
-  if (Compare(JID_EMPTY) == 0) {
-    LOG(LS_VERBOSE) << "Warning: Calling IsBare() on the empty jid";
+bool Jid::IsEmpty() const {
+  return (node_name_.empty() && domain_name_.empty() &&
+          resource_name_.empty());
+}
+
+bool Jid::IsValid() const {
+  return !domain_name_.empty();
+}
+
+bool Jid::IsBare() const {
+  if (IsEmpty()) {
+    LOG(LS_VERBOSE) << "Warning: Calling IsBare() on the empty jid.";
     return true;
   }
-  return IsValid() &&
-         data_->resource_name_.empty();
+  return IsValid() && resource_name_.empty();
 }
 
-bool
-Jid::IsFull() const {
-  return IsValid() &&
-         !data_->resource_name_.empty();
+bool Jid::IsFull() const {
+  return IsValid() && !resource_name_.empty();
 }
 
-Jid
-Jid::BareJid() const {
+Jid Jid::BareJid() const {
   if (!IsValid())
     return Jid();
   if (!IsFull())
     return *this;
-  return Jid(data_->node_name_, data_->domain_name_, STR_EMPTY);
+  return Jid(node_name_, domain_name_, STR_EMPTY);
 }
 
-#if 0
-void
-Jid::set_node(const std::string & node_name) {
-    data_->node_name_ = node_name;
-}
-void
-Jid::set_domain(const std::string & domain_name) {
-    data_->domain_name_ = domain_name;
-}
-void
-Jid::set_resource(const std::string & res_name) {
-    data_->resource_name_ = res_name;
-}
-#endif
-
-bool
-Jid::BareEquals(const Jid & other) const {
-  return (other.data_ == data_ ||
-          (data_ != NULL &&
-          other.data_ != NULL &&
-          other.data_->node_name_ == data_->node_name_ &&
-          other.data_->domain_name_ == data_->domain_name_));
+bool Jid::BareEquals(const Jid& other) const {
+  return other.node_name_ == node_name_ &&
+      other.domain_name_ == domain_name_;
 }
 
-bool
-Jid::operator==(const Jid & other) const {
-  return (other.data_ == data_ ||
-          (data_ != NULL &&
-          other.data_ != NULL &&
-          other.data_->node_name_ == data_->node_name_ &&
-          other.data_->domain_name_ == data_->domain_name_ &&
-          other.data_->resource_name_ == data_->resource_name_));
+bool Jid::operator==(const Jid& other) const {
+  return other.node_name_ == node_name_ &&
+      other.domain_name_ == domain_name_ &&
+      other.resource_name_ == resource_name_;
 }
 
-int
-Jid::Compare(const Jid & other) const {
-  if (other.data_ == data_)
-    return 0;
-  if (data_ == NULL)
-    return -1;
-  if (other.data_ == NULL)
-    return 1;
-  
+int Jid::Compare(const Jid& other) const {
   int compare_result;
-  compare_result = data_->node_name_.compare(other.data_->node_name_);
+  compare_result = node_name_.compare(other.node_name_);
   if (0 != compare_result)
     return compare_result;
-  compare_result = data_->domain_name_.compare(other.data_->domain_name_);
+  compare_result = domain_name_.compare(other.domain_name_);
   if (0 != compare_result)
     return compare_result;
-  compare_result = data_->resource_name_.compare(other.data_->resource_name_);
+  compare_result = resource_name_.compare(other.resource_name_);
   return compare_result;
 }
 
-uint32 Jid::ComputeLameHash() const {
-  uint32 hash = 0;
-  // Hash the node portion
-  {
-    const std::string &str = node();
-    for (int i = 0; i < static_cast<int>(str.size()); ++i) {
-      hash = ((hash << 2) + hash) + str[i];
-    }
-  }
-
-  // Hash the domain portion
-  {
-    const std::string &str = domain();
-    for (int i = 0; i < static_cast<int>(str.size()); ++i)
-      hash = ((hash << 2) + hash) + str[i];
-  }
-
-  // Hash the resource portion
-  {
-    const std::string &str = resource();
-    for (int i = 0; i < static_cast<int>(str.size()); ++i)
-      hash = ((hash << 2) + hash) + str[i];
-  }
-
-  return hash;
-}
-
 // --- JID parsing code: ---
 
 // Checks and normalizes the node part of a JID.
-std::string
-Jid::prepNode(const std::string str, std::string::const_iterator start, 
-    std::string::const_iterator end, bool *valid) {
+std::string Jid::PrepNode(const std::string& node, bool* valid) {
   *valid = false;
   std::string result;
 
-  for (std::string::const_iterator i = start; i < end; i++) {
+  for (std::string::const_iterator i = node.begin(); i < node.end(); ++i) {
     bool char_valid = true;
     unsigned char ch = *i;
     if (ch <= 0x7F) {
-      result += prepNodeAscii(ch, &char_valid);
+      result += PrepNodeAscii(ch, &char_valid);
     }
     else {
       // TODO: implement the correct stringprep protocol for these
@@ -302,8 +197,7 @@
 
 
 // Returns the appropriate mapping for an ASCII character in a node.
-char
-Jid::prepNodeAscii(char ch, bool *valid) {
+char Jid::PrepNodeAscii(char ch, bool* valid) {
   *valid = true;
   switch (ch) {
     case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
@@ -329,17 +223,16 @@
 
 
 // Checks and normalizes the resource part of a JID.
-std::string
-Jid::prepResource(const std::string str, std::string::const_iterator start, 
-    std::string::const_iterator end, bool *valid) {
+std::string Jid::PrepResource(const std::string& resource, bool* valid) {
   *valid = false;
   std::string result;
 
-  for (std::string::const_iterator i = start; i < end; i++) {
+  for (std::string::const_iterator i = resource.begin();
+       i < resource.end(); ++i) {
     bool char_valid = true;
     unsigned char ch = *i;
     if (ch <= 0x7F) {
-      result += prepResourceAscii(ch, &char_valid);
+      result += PrepResourceAscii(ch, &char_valid);
     }
     else {
       // TODO: implement the correct stringprep protocol for these
@@ -355,8 +248,7 @@
 }
 
 // Returns the appropriate mapping for an ASCII character in a resource.
-char
-Jid::prepResourceAscii(char ch, bool *valid) {
+char Jid::PrepResourceAscii(char ch, bool* valid) {
   *valid = true;
   switch (ch) {
     case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
@@ -373,15 +265,13 @@
 }
 
 // Checks and normalizes the domain part of a JID.
-std::string 
-Jid::prepDomain(const std::string str, std::string::const_iterator start, 
-    std::string::const_iterator end, bool *valid) {
+std::string Jid::PrepDomain(const std::string& domain, bool* valid) {
   *valid = false;
   std::string result;
 
   // TODO: if the domain contains a ':', then we should parse it
   // as an IPv6 address rather than giving an error about illegal domain.
-  prepDomain(str, start, end, &result, valid);
+  PrepDomain(domain, &result, valid);
   if (!*valid) {
     return STR_EMPTY;
   }
@@ -395,12 +285,10 @@
 
 
 // Checks and normalizes an IDNA domain.
-void
-Jid::prepDomain(const std::string str, std::string::const_iterator start, 
-    std::string::const_iterator end, std::string *buf, bool *valid) {
+void Jid::PrepDomain(const std::string& domain, std::string* buf, bool* valid) {
   *valid = false;
-  std::string::const_iterator last = start;
-  for (std::string::const_iterator i = start; i < end; i++) {
+  std::string::const_iterator last = domain.begin();
+  for (std::string::const_iterator i = domain.begin(); i < domain.end(); ++i) {
     bool label_valid = true;
     char ch = *i;
     switch (ch) {
@@ -410,7 +298,7 @@
       case 0xFF0E:
       case 0xFF61:
 #endif
-        prepDomainLabel(str, last, i, buf, &label_valid);
+        PrepDomainLabel(last, i, buf, &label_valid);
         *buf += '.';
         last = i + 1;
         break;
@@ -419,21 +307,21 @@
       return;
     }
   }
-  prepDomainLabel(str, last, end, buf, valid);
+  PrepDomainLabel(last, domain.end(), buf, valid);
 }
 
 // Checks and normalizes a domain label.
-void
-Jid::prepDomainLabel(const std::string str, std::string::const_iterator start, 
-    std::string::const_iterator end, std::string *buf, bool *valid) {
+void Jid::PrepDomainLabel(
+    std::string::const_iterator start, std::string::const_iterator end,
+    std::string* buf, bool* valid) {
   *valid = false;
 
-  int startLen = buf->length();
-  for (std::string::const_iterator i = start; i < end; i++) {
+  int start_len = buf->length();
+  for (std::string::const_iterator i = start; i < end; ++i) {
     bool char_valid = true;
     unsigned char ch = *i;
     if (ch <= 0x7F) {
-      *buf += prepDomainLabelAscii(ch, &char_valid);
+      *buf += PrepDomainLabelAscii(ch, &char_valid);
     }
     else {
       // TODO: implement ToASCII for these
@@ -444,7 +332,7 @@
     }
   }
 
-  int count = buf->length() - startLen;
+  int count = buf->length() - start_len;
   if (count == 0) {
     return;
   }
@@ -452,8 +340,8 @@
     return;
   }
 
-  // Is this check needed? See comment in prepDomainLabelAscii.
-  if ((*buf)[startLen] == '-') {
+  // Is this check needed? See comment in PrepDomainLabelAscii.
+  if ((*buf)[start_len] == '-') {
     return;
   }
   if ((*buf)[buf->length() - 1] == '-') {
@@ -464,8 +352,7 @@
 
 
 // Returns the appropriate mapping for an ASCII character in a domain label.
-char
-Jid::prepDomainLabelAscii(char ch, bool *valid) {
+char Jid::PrepDomainLabelAscii(char ch, bool* valid) {
   *valid = true;
   // TODO: A literal reading of the spec seems to say that we do
   // not need to check for these illegal characters (an "internationalized
@@ -500,4 +387,4 @@
   }
 }
 
-}
+}  // namespace buzz
diff --git a/talk/xmpp/jid.h b/talk/xmpp/jid.h
index 6831bda..9daadb0 100644
--- a/talk/xmpp/jid.h
+++ b/talk/xmpp/jid.h
@@ -24,8 +24,9 @@
  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-#ifndef _jid_h_
-#define _jid_h_
+
+#ifndef TALK_XMPP_JID_H_
+#define TALK_XMPP_JID_H_
 
 #include <string>
 #include "talk/base/basictypes.h"
@@ -33,116 +34,65 @@
 
 namespace buzz {
 
-//! The Jid class encapsulates and provides parsing help for Jids
-//! A Jid consists of three parts. The node, the domain and the resource.
-//!
-//! node@domain/resource
-//!
-//! The node and resource are both optional.  A valid jid is defined to have
-//! a domain.  A bare jid is defined to not have a resource and a full jid
-//! *does* have a resource.
+// The Jid class encapsulates and provides parsing help for Jids. A Jid
+// consists of three parts: the node, the domain and the resource, e.g.:
+//
+// node@domain/resource
+//
+// The node and resource are both optional. A valid jid is defined to have
+// a domain. A bare jid is defined to not have a resource and a full jid
+// *does* have a resource.
 class Jid {
 public:
   explicit Jid();
-  explicit Jid(const std::string & jid_string);
-  explicit Jid(const std::string & node_name,
-               const std::string & domain_name,
-               const std::string & resource_name);
-  explicit Jid(bool special, const std::string & special_string);
-  Jid(const Jid & jid) : data_(jid.data_) {
-    if (data_ != NULL) {
-      data_->AddRef();
-    }
-  }
-  Jid & operator=(const Jid & jid) {
-    if (jid.data_ != NULL) {
-      jid.data_->AddRef();
-    }
-    if (data_ != NULL) {
-      data_->Release();
-    }
-    data_ = jid.data_;
-    return *this;
-  }
-  ~Jid() {
-    if (data_ != NULL) {
-      data_->Release();
-    }
-  }
-  
+  explicit Jid(const std::string& jid_string);
+  explicit Jid(const std::string& node_name,
+               const std::string& domain_name,
+               const std::string& resource_name);
+  ~Jid();
 
-  const std::string & node() const { return !data_ ? STR_EMPTY : data_->node_name_; }
-  // void set_node(const std::string & node_name);
-  const std::string & domain() const { return !data_ ? STR_EMPTY : data_->domain_name_; }
-  // void set_domain(const std::string & domain_name);
-  const std::string & resource() const { return !data_ ? STR_EMPTY : data_->resource_name_; }
-  // void set_resource(const std::string & res_name);
+  const std::string & node() const { return node_name_; }
+  const std::string & domain() const { return domain_name_;  }
+  const std::string & resource() const { return resource_name_; }
 
   std::string Str() const;
   Jid BareJid() const;
 
+  bool IsEmpty() const;
   bool IsValid() const;
   bool IsBare() const;
   bool IsFull() const;
 
-  bool BareEquals(const Jid & other) const;
+  bool BareEquals(const Jid& other) const;
 
-  bool operator==(const Jid & other) const;
-  bool operator!=(const Jid & other) const { return !operator==(other); }
+  bool operator==(const Jid& other) const;
+  bool operator!=(const Jid& other) const { return !operator==(other); }
 
-  bool operator<(const Jid & other) const { return Compare(other) < 0; };
-  bool operator>(const Jid & other) const { return Compare(other) > 0; };
-  
+  bool operator<(const Jid& other) const { return Compare(other) < 0; };
+  bool operator>(const Jid& other) const { return Compare(other) > 0; };
+
   int Compare(const Jid & other) const;
 
-  // A quick and dirty hash.  Don't count on this producing a great 
-  // distribution.
-  uint32 ComputeLameHash() const;
-
 private:
+  void ValidateOrReset();
 
-  static std::string prepNode(const std::string str, 
-      std::string::const_iterator start, std::string::const_iterator end, 
-      bool *valid);
-  static char prepNodeAscii(char ch, bool *valid);
-  static std::string prepResource(const std::string str, 
-      std::string::const_iterator start, std::string::const_iterator end, 
-      bool *valid);
-  static char prepResourceAscii(char ch, bool *valid);
-  static std::string prepDomain(const std::string str, 
-      std::string::const_iterator start,  std::string::const_iterator end, 
-      bool *valid);
-  static void prepDomain(const std::string str, 
-      std::string::const_iterator start, std::string::const_iterator end, 
-      std::string *buf, bool *valid);
-  static void prepDomainLabel(const std::string str, 
-      std::string::const_iterator start, std::string::const_iterator end, 
-      std::string *buf, bool *valid);
-  static char prepDomainLabelAscii(char ch, bool *valid);
+  static std::string PrepNode(const std::string& node, bool* valid);
+  static char PrepNodeAscii(char ch, bool* valid);
+  static std::string PrepResource(const std::string& start, bool* valid);
+  static char PrepResourceAscii(char ch, bool* valid);
+  static std::string PrepDomain(const std::string& domain, bool* valid);
+  static void PrepDomain(const std::string& domain,
+                         std::string* buf, bool* valid);
+  static void PrepDomainLabel(
+      std::string::const_iterator start, std::string::const_iterator end,
+      std::string* buf, bool* valid);
+  static char PrepDomainLabelAscii(char ch, bool *valid);
 
-  class Data {
-  public:
-    Data() : refcount_(1) {}
-    Data(const std::string & node, const std::string &domain, const std::string & resource) :
-      node_name_(node),
-      domain_name_(domain),
-      resource_name_(resource),
-      refcount_(1) {}
-    const std::string node_name_;
-    const std::string domain_name_;
-    const std::string resource_name_;
-
-    void AddRef() { refcount_++; }
-    void Release() { if (!--refcount_) delete this; }
-  private:
-    int refcount_;
-  };
-
-  Data * data_;
+  std::string node_name_;
+  std::string domain_name_;
+  std::string resource_name_;
 };
 
 }
 
-
-
-#endif
+#endif  // TALK_XMPP_JID_H_
diff --git a/talk/xmpp/jid_unittest.cc b/talk/xmpp/jid_unittest.cc
new file mode 100644
index 0000000..b9597da
--- /dev/null
+++ b/talk/xmpp/jid_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright 2004 Google Inc. All Rights Reserved
+
+
+#include "talk/base/gunit.h"
+#include "talk/xmpp/jid.h"
+
+using buzz::Jid;
+
+TEST(JidTest, TestDomain) {
+  Jid jid("dude");
+  EXPECT_EQ("", jid.node());
+  EXPECT_EQ("dude", jid.domain());
+  EXPECT_EQ("", jid.resource());
+  EXPECT_EQ("dude", jid.Str());
+  EXPECT_EQ("dude", jid.BareJid().Str());
+  EXPECT_TRUE(jid.IsValid());
+  EXPECT_TRUE(jid.IsBare());
+  EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestNodeDomain) {
+  Jid jid("walter@dude");
+  EXPECT_EQ("walter", jid.node());
+  EXPECT_EQ("dude", jid.domain());
+  EXPECT_EQ("", jid.resource());
+  EXPECT_EQ("walter@dude", jid.Str());
+  EXPECT_EQ("walter@dude", jid.BareJid().Str());
+  EXPECT_TRUE(jid.IsValid());
+  EXPECT_TRUE(jid.IsBare());
+  EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestDomainResource) {
+  Jid jid("dude/bowlingalley");
+  EXPECT_EQ("", jid.node());
+  EXPECT_EQ("dude", jid.domain());
+  EXPECT_EQ("bowlingalley", jid.resource());
+  EXPECT_EQ("dude/bowlingalley", jid.Str());
+  EXPECT_EQ("dude", jid.BareJid().Str());
+  EXPECT_TRUE(jid.IsValid());
+  EXPECT_FALSE(jid.IsBare());
+  EXPECT_TRUE(jid.IsFull());
+}
+
+TEST(JidTest, TestNodeDomainResource) {
+  Jid jid("walter@dude/bowlingalley");
+  EXPECT_EQ("walter", jid.node());
+  EXPECT_EQ("dude", jid.domain());
+  EXPECT_EQ("bowlingalley", jid.resource());
+  EXPECT_EQ("walter@dude/bowlingalley", jid.Str());
+  EXPECT_EQ("walter@dude", jid.BareJid().Str());
+  EXPECT_TRUE(jid.IsValid());
+  EXPECT_FALSE(jid.IsBare());
+  EXPECT_TRUE(jid.IsFull());
+}
+
+TEST(JidTest, TestNode) {
+  Jid jid("walter@");
+  EXPECT_EQ("", jid.node());
+  EXPECT_EQ("", jid.domain());
+  EXPECT_EQ("", jid.resource());
+  EXPECT_EQ("", jid.Str());
+  EXPECT_EQ("", jid.BareJid().Str());
+  EXPECT_FALSE(jid.IsValid());
+  EXPECT_TRUE(jid.IsBare());
+  EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestResource) {
+  Jid jid("/bowlingalley");
+  EXPECT_EQ("", jid.node());
+  EXPECT_EQ("", jid.domain());
+  EXPECT_EQ("", jid.resource());
+  EXPECT_EQ("", jid.Str());
+  EXPECT_EQ("", jid.BareJid().Str());
+  EXPECT_FALSE(jid.IsValid());
+  EXPECT_TRUE(jid.IsBare());
+  EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestNodeResource) {
+  Jid jid("walter@/bowlingalley");
+  EXPECT_EQ("", jid.node());
+  EXPECT_EQ("", jid.domain());
+  EXPECT_EQ("", jid.resource());
+  EXPECT_EQ("", jid.Str());
+  EXPECT_EQ("", jid.BareJid().Str());
+  EXPECT_FALSE(jid.IsValid());
+  EXPECT_TRUE(jid.IsBare());
+  EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestFunky) {
+  Jid jid("bowling@muchat/walter@dude");
+  EXPECT_EQ("bowling", jid.node());
+  EXPECT_EQ("muchat", jid.domain());
+  EXPECT_EQ("walter@dude", jid.resource());
+  EXPECT_EQ("bowling@muchat/walter@dude", jid.Str());
+  EXPECT_EQ("bowling@muchat", jid.BareJid().Str());
+  EXPECT_TRUE(jid.IsValid());
+  EXPECT_FALSE(jid.IsBare());
+  EXPECT_TRUE(jid.IsFull());
+}
+
+TEST(JidTest, TestFunky2) {
+  Jid jid("muchat/walter@dude");
+  EXPECT_EQ("", jid.node());
+  EXPECT_EQ("muchat", jid.domain());
+  EXPECT_EQ("walter@dude", jid.resource());
+  EXPECT_EQ("muchat/walter@dude", jid.Str());
+  EXPECT_EQ("muchat", jid.BareJid().Str());
+  EXPECT_TRUE(jid.IsValid());
+  EXPECT_FALSE(jid.IsBare());
+  EXPECT_TRUE(jid.IsFull());
+}
diff --git a/talk/xmpp/module.h b/talk/xmpp/module.h
new file mode 100644
index 0000000..75a190d
--- /dev/null
+++ b/talk/xmpp/module.h
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _module_h_
+#define _module_h_
+
+namespace buzz {
+
+class XmppEngine;
+enum XmppReturnStatus;
+
+//! This is the base class for extension modules.
+//! An engine is registered with the module and the module then hooks the
+//! appropriate parts of the engine to implement that set of features.  It is
+//! important to unregister modules before destructing the engine.
+class XmppModule {
+public:
+  virtual ~XmppModule() {}
+
+  //! Register the engine with the module.  Only one engine can be associated
+  //! with a module at a time.  This method will return an error if there is
+  //! already an engine registered.
+  virtual XmppReturnStatus RegisterEngine(XmppEngine* engine) = 0;
+};
+
+}
+#endif
diff --git a/talk/xmpp/moduleimpl.cc b/talk/xmpp/moduleimpl.cc
new file mode 100644
index 0000000..b23ca29
--- /dev/null
+++ b/talk/xmpp/moduleimpl.cc
@@ -0,0 +1,65 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/common.h"
+#include "talk/xmpp/moduleimpl.h"
+
+namespace buzz {
+
+XmppModuleImpl::XmppModuleImpl() :
+  engine_(NULL),
+  stanza_handler_(this) {
+}
+
+XmppModuleImpl::~XmppModuleImpl()
+{
+  if (engine_ != NULL) {
+    engine_->RemoveStanzaHandler(&stanza_handler_);
+    engine_ = NULL;
+  }
+}
+
+XmppReturnStatus
+XmppModuleImpl::RegisterEngine(XmppEngine* engine)
+{
+  if (NULL == engine || NULL != engine_)
+    return XMPP_RETURN_BADARGUMENT;
+
+  engine->AddStanzaHandler(&stanza_handler_);
+  engine_ = engine;
+
+  return XMPP_RETURN_OK;
+}
+
+XmppEngine*
+XmppModuleImpl::engine() {
+  ASSERT(NULL != engine_);
+  return engine_;
+}
+
+}
+
diff --git a/talk/xmpp/moduleimpl.h b/talk/xmpp/moduleimpl.h
new file mode 100644
index 0000000..085c83a
--- /dev/null
+++ b/talk/xmpp/moduleimpl.h
@@ -0,0 +1,93 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _moduleimpl_h_
+#define _moduleimpl_h_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/module.h"
+
+namespace buzz {
+
+//! This is the base implementation class for extension modules.
+//! An engine is registered with the module and the module then hooks the
+//! appropriate parts of the engine to implement that set of features.  It is
+//! important to unregister modules before destructing the engine.
+class XmppModuleImpl {
+protected:
+  XmppModuleImpl();
+  virtual ~XmppModuleImpl();
+
+  //! Register the engine with the module.  Only one engine can be associated
+  //! with a module at a time.  This method will return an error if there is
+  //! already an engine registered.
+  XmppReturnStatus RegisterEngine(XmppEngine* engine);
+
+  //! Gets the engine that this module is attached to.
+  XmppEngine* engine();
+
+  //! Process the given stanza.
+  //! The module must return true if it has handled the stanza.
+  //! A false return value causes the stanza to be passed on to
+  //! the next registered handler.
+  virtual bool HandleStanza(const XmlElement *) { return false; };
+
+private:
+
+  //! The ModuleSessionHelper nested class allows the Module
+  //! to hook into and get stanzas and events from the engine.
+  class ModuleStanzaHandler : public XmppStanzaHandler {
+    friend class XmppModuleImpl;
+
+    ModuleStanzaHandler(XmppModuleImpl* module) :
+      module_(module) {
+    }
+
+    bool HandleStanza(const XmlElement* stanza) {
+      return module_->HandleStanza(stanza);
+    }
+
+    XmppModuleImpl* module_;
+  };
+
+  friend class ModuleStanzaHandler;
+
+  XmppEngine* engine_;
+  ModuleStanzaHandler stanza_handler_;
+};
+
+
+// This macro will implement the XmppModule interface for a class
+// that derives from both XmppModuleImpl and XmppModule
+#define IMPLEMENT_XMPPMODULE \
+  XmppReturnStatus RegisterEngine(XmppEngine* engine) { \
+    return XmppModuleImpl::RegisterEngine(engine); \
+  }
+
+}
+
+#endif
diff --git a/talk/xmpp/mucroomconfigtask_unittest.cc b/talk/xmpp/mucroomconfigtask_unittest.cc
new file mode 100644
index 0000000..e0a8aca
--- /dev/null
+++ b/talk/xmpp/mucroomconfigtask_unittest.cc
@@ -0,0 +1,144 @@
+/*
+ * 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 <string>
+#include <vector>
+
+#include "talk/base/faketaskrunner.h"
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/fakexmppclient.h"
+#include "talk/xmpp/mucroomconfigtask.h"
+
+class MucRoomConfigListener : public sigslot::has_slots<> {
+ public:
+  MucRoomConfigListener() : result_count(0), error_count(0) {}
+
+  void OnResult(buzz::MucRoomConfigTask*) {
+    ++result_count;
+  }
+
+  void OnError(buzz::IqTask* task,
+               const buzz::XmlElement* error) {
+    ++error_count;
+  }
+
+  int result_count;
+  int error_count;
+};
+
+class MucRoomConfigTaskTest : public testing::Test {
+ public:
+  MucRoomConfigTaskTest() :
+      room_jid("muc-jid-ponies@domain.com"),
+      room_name("ponies") {
+  }
+
+  virtual void SetUp() {
+    runner = new talk_base::FakeTaskRunner();
+    xmpp_client = new buzz::FakeXmppClient(runner);
+    listener = new MucRoomConfigListener();
+  }
+
+  virtual void TearDown() {
+    delete listener;
+    // delete xmpp_client;  Deleted by deleting runner.
+    delete runner;
+  }
+
+  talk_base::FakeTaskRunner* runner;
+  buzz::FakeXmppClient* xmpp_client;
+  MucRoomConfigListener* listener;
+  buzz::Jid room_jid;
+  std::string room_name;
+};
+
+TEST_F(MucRoomConfigTaskTest, TestConfigEnterprise) {
+  ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+  std::vector<std::string> room_features;
+  room_features.push_back("feature1");
+  room_features.push_back("feature2");
+  buzz::MucRoomConfigTask* task = new buzz::MucRoomConfigTask(
+      xmpp_client, room_jid, "ponies", room_features);
+  EXPECT_EQ(room_jid, task->room_jid());
+
+  task->SignalResult.connect(listener, &MucRoomConfigListener::OnResult);
+  task->Start();
+
+  std::string expected_iq =
+      "<cli:iq type=\"set\" to=\"muc-jid-ponies@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<query xmlns=\"http://jabber.org/protocol/muc#owner\">"
+          "<x xmlns=\"jabber:x:data\" type=\"form\">"
+            "<field var=\"muc#roomconfig_roomname\" type=\"text-single\">"
+              "<value>ponies</value>"
+            "</field>"
+            "<field var=\"muc#roomconfig_features\" type=\"list-multi\">"
+              "<value>feature1</value>"
+              "<value>feature2</value>"
+            "</field>"
+          "</x>"
+        "</query>"
+      "</cli:iq>";
+
+  ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+  EXPECT_EQ(0, listener->result_count);
+  EXPECT_EQ(0, listener->error_count);
+
+  std::string response_iq =
+      "<iq xmlns='jabber:client' id='0' type='result'"
+      "  from='muc-jid-ponies@domain.com'>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+  EXPECT_EQ(1, listener->result_count);
+  EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(MucRoomConfigTaskTest, TestError) {
+  std::vector<std::string> room_features;
+  buzz::MucRoomConfigTask* task = new buzz::MucRoomConfigTask(
+      xmpp_client, room_jid, "ponies", room_features);
+  task->SignalError.connect(listener, &MucRoomConfigListener::OnError);
+  task->Start();
+
+  std::string error_iq =
+      "<iq xmlns='jabber:client' id='0' type='error'"
+      " from='muc-jid-ponies@domain.com'>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq));
+
+  EXPECT_EQ(0, listener->result_count);
+  EXPECT_EQ(1, listener->error_count);
+}
diff --git a/talk/xmpp/mucroomhistorytask.cc b/talk/xmpp/mucroomhistorytask.cc
new file mode 100644
index 0000000..08c907a
--- /dev/null
+++ b/talk/xmpp/mucroomhistorytask.cc
@@ -0,0 +1,146 @@
+/*
+ * 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/mucroomhistorytask.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+// TODO: Move these to xmpp/constants.cc once it's publicly
+// viewable.
+const char NS_GOOGLE_SETTING[] = "google:setting";
+const StaticQName QN_MEETING_HISTORY = { NS_GOOGLE_SETTING, "meetinghistory" };
+const StaticQName QN_MEETING_ITEM = { NS_GOOGLE_SETTING, "item" };
+
+MucRoomHistoryGetTask::MucRoomHistoryGetTask(XmppTaskParentInterface* parent,
+                                             const buzz::Jid& user_jid)
+    : IqTask(parent, STR_GET, user_jid, MakeRequest()) {
+}
+
+//    <iq type='get' id='user-setting-1'>
+//      <meetinghistory xmlns='google:setting' />
+//    </iq>
+XmlElement* MucRoomHistoryGetTask::MakeRequest() {
+  XmlElement* history = new XmlElement(QN_MEETING_HISTORY, true);
+  return history;
+}
+
+//   <iq type='result'
+//       to='romeo@gmail.com/orchard'
+//       id='user-setting-1'>
+//     <meetinghistory xmlns='google:setting'>
+//       <item jid=’private-muc-abc-...@groupchat.google.com’ name=’Hangout'
+//             time=’2011-01-31T13:20:00Z’ />
+//       <item jid=’private-muc-abc-...@groupchat.google.com’ name=’Planning’
+//             time=’2011-02-11T 14:20:00Z />
+//       <item jid=’private-muc-abc-...@groupchat.google.com’ name=’TGIF’
+//             time=’2011-03-21T15:20:00Z’ />
+//     </meetinghistory>
+//   </iq>
+void MucRoomHistoryGetTask::HandleResult(const XmlElement* stanza) {
+  std::vector<MucRoomHistoryInfo> meeting_rooms;
+
+  const XmlElement* history = stanza->FirstNamed(QN_MEETING_HISTORY);
+  if (history == NULL) {
+    SignalResult(meeting_rooms);
+    return;
+  }
+
+  const XmlElement* history_item = history->FirstNamed(QN_MEETING_ITEM);
+  while (history_item != NULL) {
+    MucRoomHistoryInfo room;
+    room.room_name = history_item->Attr(QN_NAME);
+    room.last_enter_time = history_item->Attr(QN_TIME);
+    meeting_rooms.push_back(room);
+
+    history_item = history_item->NextNamed(QN_MEETING_ITEM);
+  }
+
+  SignalResult(meeting_rooms);
+}
+
+MucRoomHistorySetTask::MucRoomHistorySetTask(XmppTaskParentInterface* parent,
+                                             MucRoomHistoryTaskCommand command,
+                                             const buzz::Jid& user_jid,
+                                             const std::string& room_name)
+    : IqTask(parent, STR_SET, user_jid, MakeRequest(command, room_name)) {
+}
+
+// ADD
+//   <iq type='set'
+//       to='romeo@gmail.com'
+//       id='user-setting-3'>
+//     <meetinghistory xmlns='google:setting'>
+//       <item jid=’private-muc-abc@groupchat.google.com’
+//             name=’Hangout' action=’add’ />
+//     </meetinghistory>
+//   </iq>
+
+// DELETE
+//   <iq type='set'
+//       to='romeo@gmail.com'
+//       id='user-setting-3'>
+//     <meetinghistory xmlns='google:setting'>
+//       <item jid=’private-muc-abc@groupchat.google.com’
+//             name=’Hangout' action=’remove’ />
+//     </meetinghistory>
+//   </iq>
+
+// CLEAR ALL
+//   <iq type='set'
+//       from='romeo@gmail.com/orchard'
+//       to='romeo@gmail.com'
+//       id='user-setting-3'>
+//     <meetinghistory xmlns='google:setting' />
+//   </iq>
+XmlElement* MucRoomHistorySetTask::MakeRequest(
+    MucRoomHistoryTaskCommand command, const std::string& room_name) {
+  XmlElement* history = new XmlElement(QN_MEETING_HISTORY, true);
+  if (command != HT_CLEAR_ALL) {
+    XmlElement* item = new XmlElement(QN_MEETING_ITEM, false);
+    // TODO - remove QN_JID attribute when 21446107 is submitted &
+    // deployed.  no harm to leave in after the change is made.
+    item->AddAttr(QN_JID, room_name);
+    item->AddAttr(QN_NAME, room_name);
+    item->AddAttr(QN_ACTION, command == HT_ADD ? "add" : "remove");
+
+    history->AddElement(item);
+  }
+
+  return history;
+}
+
+// MODIFY & CLEAR
+//   <iq type='result'
+//       to='romeo@gmail.com/orchard'
+//       from='romeo@gmail.com'
+//       id='user-setting-3' />
+void MucRoomHistorySetTask::HandleResult(const XmlElement* stanza) {
+  SignalResult();
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/mucroomhistorytask.h b/talk/xmpp/mucroomhistorytask.h
new file mode 100644
index 0000000..79d1bfa
--- /dev/null
+++ b/talk/xmpp/mucroomhistorytask.h
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+// https://docs.google.com/a/google.com/document/d/1PObnyJbdEWDOrgDZJFIH1peMvRoUkSTv6382zq3hbuM/edit?hl=en#
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/iqtask.h"
+
+namespace buzz {
+
+struct MucRoomHistoryInfo {
+  std::string room_name;
+  std::string last_enter_time;
+};
+
+enum MucRoomHistoryTaskCommand {
+  HT_ADD = 0,
+  HT_CLEAR_ALL = 1,
+  HT_DELETE = 2,
+};
+
+class MucRoomHistoryGetTask : public IqTask {
+ public:
+  MucRoomHistoryGetTask(XmppTaskParentInterface* parent,
+                        const buzz::Jid& user_jid);
+
+  sigslot::signal1<const std::vector<MucRoomHistoryInfo>&> SignalResult;
+
+ protected:
+  static XmlElement* MakeRequest();
+  virtual void HandleResult(const XmlElement* stanza);
+};
+
+class MucRoomHistorySetTask : public IqTask {
+ public:
+  MucRoomHistorySetTask(XmppTaskParentInterface* parent,
+                        MucRoomHistoryTaskCommand command,
+                        const buzz::Jid& user_jid,
+                        const std::string& room_name);
+
+  sigslot::signal0<> SignalResult;
+
+ protected:
+  static XmlElement* MakeRequest(MucRoomHistoryTaskCommand command,
+                                 const std::string& room_name);
+  virtual void HandleResult(const XmlElement* stanza);
+};
+
+}  // namespace buzz
diff --git a/talk/xmpp/mucroomlookuptask_unittest.cc b/talk/xmpp/mucroomlookuptask_unittest.cc
new file mode 100644
index 0000000..99a78c7
--- /dev/null
+++ b/talk/xmpp/mucroomlookuptask_unittest.cc
@@ -0,0 +1,162 @@
+/*
+ * 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 <string>
+#include <vector>
+
+#include "talk/base/faketaskrunner.h"
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/fakexmppclient.h"
+#include "talk/xmpp/mucroomlookuptask.h"
+
+class MucRoomLookupListener : public sigslot::has_slots<> {
+ public:
+  MucRoomLookupListener() : error_count(0) {}
+
+  void OnResult(buzz::MucRoomLookupTask* task,
+                const buzz::MucRoomInfo& room) {
+    last_room = room;
+  }
+
+  void OnError(buzz::IqTask* task,
+               const buzz::XmlElement* error) {
+    ++error_count;
+  }
+
+  buzz::MucRoomInfo last_room;
+  int error_count;
+};
+
+class MucRoomLookupTaskTest : public testing::Test {
+ public:
+  MucRoomLookupTaskTest() :
+      lookup_server_jid("lookup@domain.com"),
+      room_jid("muc-jid-ponies@domain.com"),
+      room_name("ponies"),
+      room_domain("domain.com"),
+      room_full_name("ponies@domain.com") {
+  }
+
+  virtual void SetUp() {
+    runner = new talk_base::FakeTaskRunner();
+    xmpp_client = new buzz::FakeXmppClient(runner);
+    listener = new MucRoomLookupListener();
+  }
+
+  virtual void TearDown() {
+    delete listener;
+    // delete xmpp_client;  Deleted by deleting runner.
+    delete runner;
+  }
+
+  talk_base::FakeTaskRunner* runner;
+  buzz::FakeXmppClient* xmpp_client;
+  MucRoomLookupListener* listener;
+  buzz::Jid lookup_server_jid;
+  buzz::Jid room_jid;
+  std::string room_name;
+  std::string room_domain;
+  std::string room_full_name;
+};
+
+TEST_F(MucRoomLookupTaskTest, TestLookupName) {
+  ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+  buzz::MucRoomLookupTask* task = new buzz::MucRoomLookupTask(
+      xmpp_client, lookup_server_jid, room_name, room_domain);
+  task->SignalResult.connect(listener, &MucRoomLookupListener::OnResult);
+  task->Start();
+
+  std::string expected_iq =
+      "<cli:iq type=\"set\" to=\"lookup@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<query xmlns=\"jabber:iq:search\">"
+          "<room-name>ponies</room-name>"
+          "<room-domain>domain.com</room-domain>"
+        "</query>"
+      "</cli:iq>";
+
+  ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+  EXPECT_EQ("", listener->last_room.name);
+
+  std::string response_iq =
+      "<iq xmlns='jabber:client' from='lookup@domain.com' id='0' type='result'>"
+      "  <query xmlns='jabber:iq:search'>"
+      "    <item jid='muc-jid-ponies@domain.com'>"
+      "      <room-name>ponies</room-name>"
+      "      <room-domain>domain.com</room-domain>"
+      "    </item>"
+      "  </query>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+  EXPECT_EQ(room_name, listener->last_room.name);
+  EXPECT_EQ(room_domain, listener->last_room.domain);
+  EXPECT_EQ(room_jid, listener->last_room.jid);
+  EXPECT_EQ(room_full_name, listener->last_room.full_name());
+  EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(MucRoomLookupTaskTest, TestError) {
+  buzz::MucRoomLookupTask* task = new buzz::MucRoomLookupTask(
+      xmpp_client, lookup_server_jid, room_name, room_domain);
+  task->SignalError.connect(listener, &MucRoomLookupListener::OnError);
+  task->Start();
+
+  std::string error_iq =
+      "<iq xmlns='jabber:client' id='0' type='error'"
+      "  from='lookup@domain.com'>"
+      "</iq>";
+
+  EXPECT_EQ(0, listener->error_count);
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq));
+  EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(MucRoomLookupTaskTest, TestBadJid) {
+  buzz::MucRoomLookupTask* task = new buzz::MucRoomLookupTask(
+      xmpp_client, lookup_server_jid, room_name, room_domain);
+  task->SignalError.connect(listener, &MucRoomLookupListener::OnError);
+  task->Start();
+
+  std::string response_iq =
+      "<iq xmlns='jabber:client' from='lookup@domain.com' id='0' type='result'>"
+      "  <query xmlns='jabber:iq:search'>"
+      "    <item/>"
+      "  </query>"
+      "</iq>";
+
+  EXPECT_EQ(0, listener->error_count);
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+  EXPECT_EQ(1, listener->error_count);
+}
diff --git a/talk/xmpp/pubsub_task.cc b/talk/xmpp/pubsub_task.cc
new file mode 100644
index 0000000..97e0d4d
--- /dev/null
+++ b/talk/xmpp/pubsub_task.cc
@@ -0,0 +1,217 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/pubsub_task.h"
+
+#include <map>
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+
+PubsubTask::PubsubTask(XmppTaskParentInterface* parent,
+                       const buzz::Jid& pubsub_node_jid)
+    : buzz::XmppTask(parent, buzz::XmppEngine::HL_SENDER),
+      pubsub_node_jid_(pubsub_node_jid) {
+}
+
+PubsubTask::~PubsubTask() {
+}
+
+// Checks for pubsub publish events as well as responses to get IQs.
+bool PubsubTask::HandleStanza(const buzz::XmlElement* stanza) {
+  const buzz::QName& stanza_name(stanza->Name());
+  if (stanza_name == buzz::QN_MESSAGE) {
+    if (MatchStanzaFrom(stanza, pubsub_node_jid_)) {
+      const buzz::XmlElement* pubsub_event_item =
+          stanza->FirstNamed(QN_PUBSUB_EVENT);
+      if (pubsub_event_item != NULL) {
+        QueueStanza(pubsub_event_item);
+        return true;
+      }
+    }
+  } else if (stanza_name == buzz::QN_IQ) {
+    if (MatchResponseIq(stanza, pubsub_node_jid_, task_id())) {
+      const buzz::XmlElement* pubsub_item = stanza->FirstNamed(QN_PUBSUB);
+      if (pubsub_item != NULL) {
+        QueueStanza(pubsub_item);
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+int PubsubTask::ProcessResponse() {
+  const buzz::XmlElement* stanza = NextStanza();
+  if (stanza == NULL) {
+    return STATE_BLOCKED;
+  }
+
+  if (stanza->Attr(buzz::QN_TYPE) == buzz::STR_ERROR) {
+    OnPubsubError(stanza->FirstNamed(buzz::QN_ERROR));
+    return STATE_RESPONSE;
+  }
+
+  const buzz::QName& stanza_name(stanza->Name());
+  if (stanza_name == QN_PUBSUB_EVENT) {
+    HandlePubsubEventMessage(stanza);
+  } else if (stanza_name == QN_PUBSUB) {
+    HandlePubsubIqGetResponse(stanza);
+  }
+
+  return STATE_RESPONSE;
+}
+
+// Registers a function pointer to be called when the value of the pubsub
+// node changes.
+// Note that this does not actually change the XMPP pubsub
+// subscription. All publish events are always received by everyone in the
+// MUC. This function just controls whether the handle function will get
+// called when the event is received.
+bool PubsubTask::SubscribeToNode(const std::string& pubsub_node,
+                                 NodeHandler handler) {
+  subscribed_nodes_[pubsub_node] = handler;
+  talk_base::scoped_ptr<buzz::XmlElement> get_iq_request(
+      MakeIq(buzz::STR_GET, pubsub_node_jid_, task_id()));
+  if (get_iq_request.get() == NULL) {
+    return false;
+  }
+  buzz::XmlElement* pubsub_element = new buzz::XmlElement(QN_PUBSUB, true);
+  buzz::XmlElement* items_element = new buzz::XmlElement(QN_PUBSUB_ITEMS, true);
+
+  items_element->AddAttr(buzz::QN_NODE, pubsub_node);
+  pubsub_element->AddElement(items_element);
+  get_iq_request->AddElement(pubsub_element);
+
+  if (SendStanza(get_iq_request.get()) != buzz::XMPP_RETURN_OK) {
+    return false;
+  }
+
+  return true;
+}
+
+void PubsubTask::UnsubscribeFromNode(const std::string& pubsub_node) {
+  subscribed_nodes_.erase(pubsub_node);
+}
+
+void PubsubTask::OnPubsubError(const buzz::XmlElement* error_stanza) {
+}
+
+// Checks for a pubsub event message like the following:
+//
+//  <message from="muvc-private-chat-some-id@groupchat.google.com"
+//   to="john@site.com/gcomm582B14C9">
+//    <event xmlns:"http://jabber.org/protocol/pubsub#event">
+//      <items node="node-name">
+//        <item id="some-id">
+//          <payload/>
+//        </item>
+//      </items>
+//    </event>
+//  </message>
+//
+// It also checks for retraction event messages like the following:
+//
+//  <message from="muvc-private-chat-some-id@groupchat.google.com"
+//   to="john@site.com/gcomm582B14C9">
+//    <event xmlns:"http://jabber.org/protocol/pubsub#event">
+//      <items node="node-name">
+//        <retract id="some-id"/>
+//      </items>
+//    </event>
+//  </message>
+void PubsubTask::HandlePubsubEventMessage(
+    const buzz::XmlElement* pubsub_event) {
+  ASSERT(pubsub_event->Name() == QN_PUBSUB_EVENT);
+  for (const buzz::XmlChild* child = pubsub_event->FirstChild();
+       child != NULL;
+       child = child->NextChild()) {
+    const buzz::XmlElement* child_element = child->AsElement();
+    const buzz::QName& child_name(child_element->Name());
+    if (child_name == QN_PUBSUB_EVENT_ITEMS) {
+      HandlePubsubItems(child_element);
+    }
+  }
+}
+
+// Checks for a response to an pubsub IQ get like the following:
+//
+//  <iq from="muvc-private-chat-some-id@groupchat.google.com"
+//   to="john@site.com/gcomm582B14C9"
+//   type="result">
+//    <pubsub xmlns:"http://jabber.org/protocol/pubsub">
+//      <items node="node-name">
+//        <item id="some-id">
+//          <payload/>
+//        </item>
+//      </items>
+//    </event>
+//  </message>
+void PubsubTask::HandlePubsubIqGetResponse(
+    const buzz::XmlElement* pubsub_iq_response) {
+  ASSERT(pubsub_iq_response->Name() == QN_PUBSUB);
+  for (const buzz::XmlChild* child = pubsub_iq_response->FirstChild();
+       child != NULL;
+       child = child->NextChild()) {
+    const buzz::XmlElement* child_element = child->AsElement();
+    const buzz::QName& child_name(child_element->Name());
+    if (child_name == QN_PUBSUB_ITEMS) {
+      HandlePubsubItems(child_element);
+    }
+  }
+}
+
+// Calls registered handlers in response to pubsub event or response to
+// IQ pubsub get.
+// 'items' is the child of a pubsub#event:event node or pubsub:pubsub node.
+void PubsubTask::HandlePubsubItems(const buzz::XmlElement* items) {
+  ASSERT(items->HasAttr(QN_NODE));
+  const std::string& node_name(items->Attr(QN_NODE));
+  NodeSubscriptions::iterator iter = subscribed_nodes_.find(node_name);
+  if (iter != subscribed_nodes_.end()) {
+    NodeHandler handler = iter->second;
+    const buzz::XmlElement* item = items->FirstElement();
+    while (item != NULL) {
+      const buzz::QName& item_name(item->Name());
+      if (item_name != QN_PUBSUB_EVENT_ITEM &&
+          item_name != QN_PUBSUB_EVENT_RETRACT &&
+          item_name != QN_PUBSUB_ITEM) {
+        continue;
+      }
+
+      (this->*handler)(item);
+      item = item->NextElement();
+    }
+    return;
+  }
+}
+
+}
diff --git a/talk/xmpp/pubsub_task.h b/talk/xmpp/pubsub_task.h
new file mode 100644
index 0000000..45a7462
--- /dev/null
+++ b/talk/xmpp/pubsub_task.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_XMPP_PUBSUB_TASK_H_
+#define TALK_XMPP_PUBSUB_TASK_H_
+
+#include <map>
+#include <string>
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// Base class to help write pubsub tasks.
+// In ProcessStart call SubscribeNode with namespaces of interest along with
+// NodeHandlers.
+// When pubsub notifications arrive and matches the namespace, the NodeHandlers
+// will be called back.
+class PubsubTask : public buzz::XmppTask {
+ public:
+  virtual ~PubsubTask();
+
+ protected:
+  typedef void (PubsubTask::*NodeHandler)(const buzz::XmlElement* node);
+
+  PubsubTask(XmppTaskParentInterface* parent, const buzz::Jid& pubsub_node_jid);
+
+  virtual bool HandleStanza(const buzz::XmlElement* stanza);
+  virtual int ProcessResponse();
+
+  bool SubscribeToNode(const std::string& pubsub_node, NodeHandler handler);
+  void UnsubscribeFromNode(const std::string& pubsub_node);
+
+  // Called when there is an error. Derived class can do what it needs to.
+  virtual void OnPubsubError(const buzz::XmlElement* error_stanza);
+
+ private:
+  typedef std::map<std::string, NodeHandler> NodeSubscriptions;
+
+  void HandlePubsubIqGetResponse(const buzz::XmlElement* pubsub_iq_response);
+  void HandlePubsubEventMessage(const buzz::XmlElement* pubsub_event_message);
+  void HandlePubsubItems(const buzz::XmlElement* items);
+
+  buzz::Jid pubsub_node_jid_;
+  NodeSubscriptions subscribed_nodes_;
+};
+
+}  // namespace buzz
+
+#endif // TALK_XMPP_PUBSUB_TASK_H_
diff --git a/talk/xmpp/pubsubclient_unittest.cc b/talk/xmpp/pubsubclient_unittest.cc
new file mode 100644
index 0000000..2e4c511
--- /dev/null
+++ b/talk/xmpp/pubsubclient_unittest.cc
@@ -0,0 +1,271 @@
+// Copyright 2011 Google Inc. All Rights Reserved
+
+
+#include <string>
+
+#include "talk/base/faketaskrunner.h"
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/fakexmppclient.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubclient.h"
+
+struct HandledPubSubItem {
+  std::string itemid;
+  std::string payload;
+};
+
+class TestPubSubItemsListener : public sigslot::has_slots<> {
+ public:
+  TestPubSubItemsListener() : error_count(0) {}
+
+  void OnItems(buzz::PubSubClient*,
+               const std::vector<buzz::PubSubItem>& items) {
+    for (std::vector<buzz::PubSubItem>::const_iterator item = items.begin();
+         item != items.end(); ++item) {
+      HandledPubSubItem handled_item;
+      handled_item.itemid = item->itemid;
+      if (item->elem->FirstElement() != NULL) {
+        handled_item.payload = item->elem->FirstElement()->Str();
+      }
+      this->items.push_back(handled_item);
+    }
+  }
+
+  void OnRequestError(buzz::PubSubClient* client,
+                      const buzz::XmlElement* stanza) {
+    error_count++;
+  }
+
+  void OnPublishResult(buzz::PubSubClient* client,
+                       const std::string& task_id,
+                       const buzz::XmlElement* item) {
+    result_task_id = task_id;
+  }
+
+  void OnPublishError(buzz::PubSubClient* client,
+                      const std::string& task_id,
+                      const buzz::XmlElement* item,
+                      const buzz::XmlElement* stanza) {
+    error_count++;
+    error_task_id = task_id;
+  }
+
+  void OnRetractResult(buzz::PubSubClient* client,
+                       const std::string& task_id) {
+    result_task_id = task_id;
+  }
+
+  void OnRetractError(buzz::PubSubClient* client,
+                      const std::string& task_id,
+                      const buzz::XmlElement* stanza) {
+    error_count++;
+    error_task_id = task_id;
+  }
+
+  std::vector<HandledPubSubItem> items;
+  int error_count;
+  std::string error_task_id;
+  std::string result_task_id;
+};
+
+class PubSubClientTest : public testing::Test {
+ public:
+  PubSubClientTest() :
+      pubsubjid("room@domain.com"),
+      node("topic"),
+      itemid("key") {
+    runner.reset(new talk_base::FakeTaskRunner());
+    xmpp_client = new buzz::FakeXmppClient(runner.get());
+    client.reset(new buzz::PubSubClient(xmpp_client, pubsubjid, node));
+    listener.reset(new TestPubSubItemsListener());
+    client->SignalItems.connect(
+        listener.get(), &TestPubSubItemsListener::OnItems);
+    client->SignalRequestError.connect(
+        listener.get(), &TestPubSubItemsListener::OnRequestError);
+    client->SignalPublishResult.connect(
+        listener.get(), &TestPubSubItemsListener::OnPublishResult);
+    client->SignalPublishError.connect(
+        listener.get(), &TestPubSubItemsListener::OnPublishError);
+    client->SignalRetractResult.connect(
+        listener.get(), &TestPubSubItemsListener::OnRetractResult);
+    client->SignalRetractError.connect(
+        listener.get(), &TestPubSubItemsListener::OnRetractError);
+  }
+
+  talk_base::scoped_ptr<talk_base::FakeTaskRunner> runner;
+  // xmpp_client deleted by deleting runner.
+  buzz::FakeXmppClient* xmpp_client;
+  talk_base::scoped_ptr<buzz::PubSubClient> client;
+  talk_base::scoped_ptr<TestPubSubItemsListener> listener;
+  buzz::Jid pubsubjid;
+  std::string node;
+  std::string itemid;
+};
+
+TEST_F(PubSubClientTest, TestRequest) {
+  client->RequestItems();
+
+  std::string expected_iq =
+      "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+          "<pub:items node=\"topic\"/>"
+        "</pub:pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+      "  <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+      "    <items node='topic'>"
+      "      <item id='key0'>"
+      "        <value0a/>"
+      "      </item>"
+      "      <item id='key1'>"
+      "        <value1a/>"
+      "      </item>"
+      "    </items>"
+      "  </pubsub>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  ASSERT_EQ(2U, listener->items.size());
+  EXPECT_EQ("key0", listener->items[0].itemid);
+  EXPECT_EQ("<pub:value0a xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+            listener->items[0].payload);
+  EXPECT_EQ("key1", listener->items[1].itemid);
+  EXPECT_EQ("<pub:value1a xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+            listener->items[1].payload);
+
+  std::string items_message =
+      "<message xmlns='jabber:client' from='room@domain.com'>"
+      "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+      "    <items node='topic'>"
+      "      <item id='key0'>"
+      "        <value0b/>"
+      "      </item>"
+      "      <item id='key1'>"
+      "        <value1b/>"
+      "      </item>"
+      "    </items>"
+      "  </event>"
+      "</message>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(items_message));
+  ASSERT_EQ(4U, listener->items.size());
+  EXPECT_EQ("key0", listener->items[2].itemid);
+  EXPECT_EQ("<eve:value0b"
+            " xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+            listener->items[2].payload);
+  EXPECT_EQ("key1", listener->items[3].itemid);
+  EXPECT_EQ("<eve:value1b"
+            " xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+            listener->items[3].payload);
+}
+
+TEST_F(PubSubClientTest, TestRequestError) {
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+      "  <error type='auth'>"
+      "    <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+      "  </error>"
+      "</iq>";
+
+  client->RequestItems();
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(PubSubClientTest, TestPublish) {
+  buzz::XmlElement* payload =
+      new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+
+  std::string task_id;
+  client->PublishItem(itemid, payload, &task_id);
+
+  std::string expected_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<publish node=\"topic\">"
+            "<item id=\"key\">"
+              "<value/>"
+            "</item>"
+          "</publish>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(task_id, listener->result_task_id);
+}
+
+TEST_F(PubSubClientTest, TestPublishError) {
+  buzz::XmlElement* payload =
+      new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+
+  std::string task_id;
+  client->PublishItem(itemid, payload, &task_id);
+
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+      "  <error type='auth'>"
+      "    <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+      "  </error>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(1, listener->error_count);
+  EXPECT_EQ(task_id, listener->error_task_id);
+}
+
+TEST_F(PubSubClientTest, TestRetract) {
+  std::string task_id;
+  client->RetractItem(itemid, &task_id);
+
+  std::string expected_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<retract node=\"topic\" notify=\"true\">"
+            "<item id=\"key\"/>"
+          "</retract>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(task_id, listener->result_task_id);
+}
+
+TEST_F(PubSubClientTest, TestRetractError) {
+  std::string task_id;
+  client->RetractItem(itemid, &task_id);
+
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+      "  <error type='auth'>"
+      "    <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+      "  </error>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(1, listener->error_count);
+  EXPECT_EQ(task_id, listener->error_task_id);
+}
diff --git a/talk/xmpp/pubsubtasks_unittest.cc b/talk/xmpp/pubsubtasks_unittest.cc
new file mode 100644
index 0000000..67fc306
--- /dev/null
+++ b/talk/xmpp/pubsubtasks_unittest.cc
@@ -0,0 +1,273 @@
+// Copyright 2011 Google Inc. All Rights Reserved
+
+
+#include <string>
+
+#include "talk/base/faketaskrunner.h"
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/iqtask.h"
+#include "talk/xmpp/fakexmppclient.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubtasks.h"
+
+struct HandledPubSubItem {
+  std::string itemid;
+  std::string payload;
+};
+
+class TestPubSubTasksListener : public sigslot::has_slots<> {
+ public:
+  TestPubSubTasksListener() : result_count(0), error_count(0) {}
+
+  void OnReceiveUpdate(buzz::PubSubReceiveTask* task,
+                       const std::vector<buzz::PubSubItem>& items) {
+    OnItems(items);
+  }
+
+  void OnRequestResult(buzz::PubSubRequestTask* task,
+                       const std::vector<buzz::PubSubItem>& items) {
+    OnItems(items);
+  }
+
+  void OnItems(const std::vector<buzz::PubSubItem>& items) {
+    for (std::vector<buzz::PubSubItem>::const_iterator item = items.begin();
+         item != items.end(); ++item) {
+      HandledPubSubItem handled_item;
+      handled_item.itemid = item->itemid;
+      if (item->elem->FirstElement() != NULL) {
+        handled_item.payload = item->elem->FirstElement()->Str();
+      }
+      this->items.push_back(handled_item);
+    }
+  }
+
+  void OnPublishResult(buzz::PubSubPublishTask* task) {
+    ++result_count;
+  }
+
+  void OnRetractResult(buzz::PubSubRetractTask* task) {
+    ++result_count;
+  }
+
+  void OnError(buzz::IqTask* task, const buzz::XmlElement* stanza) {
+    ++error_count;
+  }
+
+  std::vector<HandledPubSubItem> items;
+  int result_count;
+  int error_count;
+};
+
+class PubSubTasksTest : public testing::Test {
+ public:
+  PubSubTasksTest() :
+      pubsubjid("room@domain.com"),
+      node("topic"),
+      itemid("key") {
+    runner.reset(new talk_base::FakeTaskRunner());
+    client = new buzz::FakeXmppClient(runner.get());
+    listener.reset(new TestPubSubTasksListener());
+  }
+
+  talk_base::scoped_ptr<talk_base::FakeTaskRunner> runner;
+  // Client deleted by deleting runner.
+  buzz::FakeXmppClient* client;
+  talk_base::scoped_ptr<TestPubSubTasksListener> listener;
+  buzz::Jid pubsubjid;
+  std::string node;
+  std::string itemid;
+};
+
+TEST_F(PubSubTasksTest, TestRequest) {
+  buzz::PubSubRequestTask* task =
+      new buzz::PubSubRequestTask(client, pubsubjid, node);
+  task->SignalResult.connect(
+      listener.get(), &TestPubSubTasksListener::OnRequestResult);
+  task->Start();
+
+  std::string expected_iq =
+      "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+          "<pub:items node=\"topic\"/>"
+        "</pub:pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(1U, client->sent_stanzas().size());
+  EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str());
+
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+      "  <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+      "    <items node='topic'>"
+      "      <item id='key0'>"
+      "        <value0/>"
+      "      </item>"
+      "      <item id='key1'>"
+      "        <value1/>"
+      "      </item>"
+      "    </items>"
+      "  </pubsub>"
+      "</iq>";
+
+  client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+  ASSERT_EQ(2U, listener->items.size());
+  EXPECT_EQ("key0", listener->items[0].itemid);
+  EXPECT_EQ("<pub:value0 xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+            listener->items[0].payload);
+  EXPECT_EQ("key1", listener->items[1].itemid);
+  EXPECT_EQ("<pub:value1 xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+            listener->items[1].payload);
+}
+
+TEST_F(PubSubTasksTest, TestRequestError) {
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+      "  <error type='auth'>"
+      "    <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+      "  </error>"
+      "</iq>";
+
+  buzz::PubSubRequestTask* task =
+      new buzz::PubSubRequestTask(client, pubsubjid, node);
+  task->SignalResult.connect(
+      listener.get(), &TestPubSubTasksListener::OnRequestResult);
+  task->SignalError.connect(
+      listener.get(), &TestPubSubTasksListener::OnError);
+  task->Start();
+  client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+  EXPECT_EQ(0, listener->result_count);
+  EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(PubSubTasksTest, TestReceive) {
+  std::string items_message =
+      "<message xmlns='jabber:client' from='room@domain.com'>"
+      "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+      "    <items node='topic'>"
+      "      <item id='key0'>"
+      "        <value0/>"
+      "      </item>"
+      "      <item id='key1'>"
+      "        <value1/>"
+      "      </item>"
+      "    </items>"
+      "  </event>"
+      "</message>";
+
+  buzz::PubSubReceiveTask* task =
+      new buzz::PubSubReceiveTask(client, pubsubjid, node);
+  task->SignalUpdate.connect(
+      listener.get(), &TestPubSubTasksListener::OnReceiveUpdate);
+  task->Start();
+  client->HandleStanza(buzz::XmlElement::ForStr(items_message));
+
+  ASSERT_EQ(2U, listener->items.size());
+  EXPECT_EQ("key0", listener->items[0].itemid);
+  EXPECT_EQ(
+      "<eve:value0 xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+      listener->items[0].payload);
+  EXPECT_EQ("key1", listener->items[1].itemid);
+  EXPECT_EQ(
+      "<eve:value1 xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+      listener->items[1].payload);
+}
+
+TEST_F(PubSubTasksTest, TestPublish) {
+  buzz::XmlElement* payload =
+      new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+  std::string expected_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<publish node=\"topic\">"
+            "<item id=\"key\">"
+              "<value/>"
+            "</item>"
+          "</publish>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  std::vector<buzz::XmlElement*> children;
+  children.push_back(payload);
+  buzz::PubSubPublishTask* task =
+      new buzz::PubSubPublishTask(client, pubsubjid, node, itemid, children);
+  task->SignalResult.connect(
+      listener.get(), &TestPubSubTasksListener::OnPublishResult);
+  task->Start();
+
+  ASSERT_EQ(1U, client->sent_stanzas().size());
+  EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str());
+
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+  client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+  EXPECT_EQ(1, listener->result_count);
+  EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(PubSubTasksTest, TestPublishError) {
+  buzz::XmlElement* payload =
+      new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+
+  std::vector<buzz::XmlElement*> children;
+  children.push_back(payload);
+  buzz::PubSubPublishTask* task =
+      new buzz::PubSubPublishTask(client, pubsubjid, node, itemid, children);
+  task->SignalResult.connect(
+      listener.get(), &TestPubSubTasksListener::OnPublishResult);
+  task->SignalError.connect(
+      listener.get(), &TestPubSubTasksListener::OnError);
+  task->Start();
+
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+      "  <error type='auth'>"
+      "    <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+      "  </error>"
+      "</iq>";
+
+  client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+  EXPECT_EQ(0, listener->result_count);
+  EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(PubSubTasksTest, TestRetract) {
+  buzz::PubSubRetractTask* task =
+      new buzz::PubSubRetractTask(client, pubsubjid, node, itemid);
+  task->SignalResult.connect(
+      listener.get(), &TestPubSubTasksListener::OnRetractResult);
+  task->SignalError.connect(
+      listener.get(), &TestPubSubTasksListener::OnError);
+  task->Start();
+
+  std::string expected_iq =
+      "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+          "<retract node=\"topic\" notify=\"true\">"
+            "<item id=\"key\"/>"
+          "</retract>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(1U, client->sent_stanzas().size());
+  EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str());
+
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+  client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+  EXPECT_EQ(1, listener->result_count);
+  EXPECT_EQ(0, listener->error_count);
+}
diff --git a/talk/xmpp/ratelimitmanager.h b/talk/xmpp/ratelimitmanager.h
index 1a7fc82..288084f 100644
--- a/talk/xmpp/ratelimitmanager.h
+++ b/talk/xmpp/ratelimitmanager.h
@@ -28,7 +28,7 @@
 #ifndef _RATELIMITMANAGER_H_
 #define _RATELIMITMANAGER_H_
 
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
 #include "talk/base/taskrunner.h"
 #include <map>
 
diff --git a/talk/xmpp/rostermodule.h b/talk/xmpp/rostermodule.h
new file mode 100644
index 0000000..e81c4f0
--- /dev/null
+++ b/talk/xmpp/rostermodule.h
@@ -0,0 +1,325 @@
+/*
+ * 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 _rostermodule_h_
+#define _rostermodule_h_
+
+#include "talk/xmpp/module.h"
+
+namespace buzz {
+
+class XmppRosterModule;
+
+// The main way you initialize and use the module would be like this:
+//    XmppRosterModule *roster_module = XmppRosterModule::Create();
+//    roster_module->RegisterEngine(engine);
+//    roster_module->BroadcastPresence();
+//    roster_module->RequestRosterUpdate();
+
+//! This enum captures the valid values for the show attribute in a presence
+//! stanza
+enum XmppPresenceShow
+{
+  XMPP_PRESENCE_CHAT = 0,
+  XMPP_PRESENCE_DEFAULT = 1,
+  XMPP_PRESENCE_AWAY = 2,
+  XMPP_PRESENCE_XA = 3,
+  XMPP_PRESENCE_DND = 4,
+};
+
+//! These are the valid subscription states in a roster contact.  This
+//! represents the combination of the subscription and ask attributes
+enum XmppSubscriptionState
+{
+  XMPP_SUBSCRIPTION_NONE = 0,
+  XMPP_SUBSCRIPTION_NONE_ASKED = 1,
+  XMPP_SUBSCRIPTION_TO = 2,
+  XMPP_SUBSCRIPTION_FROM = 3,
+  XMPP_SUBSCRIPTION_FROM_ASKED = 4,
+  XMPP_SUBSCRIPTION_BOTH = 5,
+};
+
+//! These represent the valid types of presence stanzas for managing
+//! subscriptions
+enum XmppSubscriptionRequestType
+{
+  XMPP_REQUEST_SUBSCRIBE = 0,
+  XMPP_REQUEST_UNSUBSCRIBE = 1,
+  XMPP_REQUEST_SUBSCRIBED = 2,
+  XMPP_REQUEST_UNSUBSCRIBED = 3,
+};
+
+enum XmppPresenceAvailable {
+  XMPP_PRESENCE_UNAVAILABLE = 0,
+  XMPP_PRESENCE_AVAILABLE   = 1,
+  XMPP_PRESENCE_ERROR       = 2,
+};
+
+//! Presence Information
+//! This class stores both presence information for outgoing presence and is
+//! returned by methods in XmppRosterModule to represent recieved incoming
+//! presence information.  When this class is writeable (non-const) then each
+//! update to any property will set the inner xml.  Setting the raw_xml will
+//! rederive all of the other properties.
+class XmppPresence {
+public:
+  virtual ~XmppPresence() {}
+
+  //! Create a new Presence
+  //! This is typically only used when sending a directed presence
+  static XmppPresence* Create();
+
+  //! The Jid of for the presence information.
+  //! Typically this will be a full Jid with resource specified.
+  virtual const Jid jid() const = 0;
+
+  //! Is the contact available?
+  virtual XmppPresenceAvailable available() const = 0;
+
+  //! Sets if the user is available or not
+  virtual XmppReturnStatus set_available(XmppPresenceAvailable available) = 0;
+
+  //! The show value of the presence info
+  virtual XmppPresenceShow presence_show() const = 0;
+
+  //! Set the presence show value
+  virtual XmppReturnStatus set_presence_show(XmppPresenceShow show) = 0;
+
+  //! The Priority of the presence info
+  virtual int priority() const = 0;
+
+  //! Set the priority of the presence
+  virtual XmppReturnStatus set_priority(int priority) = 0;
+
+  //! The plain text status of the presence info.
+  //! If there are multiple status because of language, this will either be a
+  //! status that is not tagged for language or the first available
+  virtual const std::string& status() const = 0;
+
+  //! Sets the status for the presence info.
+  //! If there is more than one status present already then this will remove
+  //! them all and replace it with one status element we no specified language
+  virtual XmppReturnStatus set_status(const std::string& status) = 0;
+
+  //! The raw xml of the presence update
+  virtual const XmlElement* raw_xml() const = 0;
+
+  //! Sets the raw presence stanza for the presence update
+  //! This will cause all other data items in this structure to be rederived
+  virtual XmppReturnStatus set_raw_xml(const XmlElement * xml) = 0;
+};
+
+//! A contact as given by the server
+class XmppRosterContact {
+public:
+  virtual ~XmppRosterContact() {}
+
+  //! Create a new roster contact
+  //! This is typically only used when doing a roster update/add
+  static XmppRosterContact* Create();
+
+  //! The jid for the contact.
+  //! Typically this will be a bare Jid.
+  virtual const Jid jid() const = 0;
+
+  //! Sets the jid for the roster contact update
+  virtual XmppReturnStatus set_jid(const Jid& jid) = 0;
+
+  //! The name (nickname) stored for this contact
+  virtual const std::string& name() const = 0;
+
+  //! Sets the name
+  virtual XmppReturnStatus set_name(const std::string& name) = 0;
+
+  //! The Presence subscription state stored on the server for this contact
+  //! This is never settable and will be ignored when generating a roster
+  //! add/update request
+  virtual XmppSubscriptionState subscription_state() const = 0;
+
+  //! The number of Groups applied to this contact
+  virtual size_t GetGroupCount() const = 0;
+
+  //! Gets a Group applied to the contact based on index.
+  //! range
+  virtual const std::string& GetGroup(size_t index) const = 0;
+
+  //! Adds a group to this contact.
+  //! This will return a bad argument error if the group is already there.
+  virtual XmppReturnStatus AddGroup(const std::string& group) = 0;
+
+  //! Removes a group from the contact.
+  //! This will return an error if the group cannot be found in the group list.
+  virtual XmppReturnStatus RemoveGroup(const std::string& group) = 0;
+
+  //! The raw xml for this roster contact
+  virtual const XmlElement* raw_xml() const = 0;
+
+  //! Sets the raw presence stanza for the contact update/add
+  //! This will cause all other data items in this structure to be rederived
+  virtual XmppReturnStatus set_raw_xml(const XmlElement * xml) = 0;
+};
+
+//! The XmppRosterHandler is an interface for callbacks from the module
+class XmppRosterHandler {
+public:
+  //! A request for a subscription has come in.
+  //! Typically, the UI will ask the user if it is okay to let the requester
+  //! get presence notifications for the user.  The response is send back
+  //! by calling ApproveSubscriber or CancelSubscriber.
+  virtual void SubscriptionRequest(XmppRosterModule* roster,
+                                   const Jid& requesting_jid,
+                                   XmppSubscriptionRequestType type,
+                                   const XmlElement* raw_xml) = 0;
+
+  //! Some type of presence error has occured
+  virtual void SubscriptionError(XmppRosterModule* roster,
+                                 const Jid& from,
+                                 const XmlElement* raw_xml) = 0;
+
+  virtual void RosterError(XmppRosterModule* roster,
+                           const XmlElement* raw_xml) = 0;
+
+  //! New presence information has come in
+  //! The user is notified with the presence object directly.  This info is also
+  //! added to the store accessable from the engine.
+  virtual void IncomingPresenceChanged(XmppRosterModule* roster,
+                                       const XmppPresence* presence) = 0;
+
+  //! A contact has changed
+  //! This indicates that the data for a contact may have changed.  No
+  //! contacts have been added or removed.
+  virtual void ContactChanged(XmppRosterModule* roster,
+                              const XmppRosterContact* old_contact,
+                              size_t index) = 0;
+
+  //! A set of contacts have been added
+  //! These contacts may have been added in response to the original roster
+  //! request or due to a "roster push" from the server.
+  virtual void ContactsAdded(XmppRosterModule* roster,
+                             size_t index, size_t number) = 0;
+
+  //! A contact has been removed
+  //! This contact has been removed form the list.
+  virtual void ContactRemoved(XmppRosterModule* roster,
+                              const XmppRosterContact* removed_contact,
+                              size_t index) = 0;
+
+};
+
+//! An XmppModule for handle roster and presence functionality
+class XmppRosterModule : public XmppModule {
+public:
+  //! Creates a new XmppRosterModule
+  static XmppRosterModule * Create();
+  virtual ~XmppRosterModule() {}
+
+  //! Sets the roster handler (callbacks) for the module
+  virtual XmppReturnStatus set_roster_handler(XmppRosterHandler * handler) = 0;
+
+  //! Gets the roster handler for the module
+  virtual XmppRosterHandler* roster_handler() = 0;
+
+  // USER PRESENCE STATE -------------------------------------------------------
+
+  //! Gets the aggregate outgoing presence
+  //! This object is non-const and be edited directly.  No update is sent
+  //! to the server until a Broadcast is sent
+  virtual XmppPresence* outgoing_presence() = 0;
+
+  //! Broadcasts that the user is available.
+  //! Nothing with respect to presence is sent until this is called.
+  virtual XmppReturnStatus BroadcastPresence() = 0;
+
+  //! Sends a directed presence to a Jid
+  //! Note that the client doesn't store where directed presence notifications
+  //! have been sent.  The server can keep the appropriate state
+  virtual XmppReturnStatus SendDirectedPresence(const XmppPresence* presence,
+                                                const Jid& to_jid) = 0;
+
+  // INCOMING PRESENCE STATUS --------------------------------------------------
+
+  //! Returns the number of incoming presence data recorded
+  virtual size_t GetIncomingPresenceCount() = 0;
+
+  //! Returns an incoming presence datum based on index
+  virtual const XmppPresence* GetIncomingPresence(size_t index) = 0;
+
+  //! Gets the number of presence data for a bare Jid
+  //! There may be a datum per resource
+  virtual size_t GetIncomingPresenceForJidCount(const Jid& jid) = 0;
+
+  //! Returns a single presence data for a Jid based on index
+  virtual const XmppPresence* GetIncomingPresenceForJid(const Jid& jid,
+                                                        size_t index) = 0;
+
+  // ROSTER MANAGEMENT ---------------------------------------------------------
+
+  //! Requests an update of the roster from the server
+  //! This must be called to initialize the client side cache of the roster
+  //! After this is sent the server should keep this module apprised of any
+  //! changes.
+  virtual XmppReturnStatus RequestRosterUpdate() = 0;
+
+  //! Returns the number of contacts in the roster
+  virtual size_t GetRosterContactCount() = 0;
+
+  //! Returns a contact by index
+  virtual const XmppRosterContact* GetRosterContact(size_t index) = 0;
+
+  //! Finds a contact by Jid
+  virtual const XmppRosterContact* FindRosterContact(const Jid& jid) = 0;
+
+  //! Send a request to the server to add a contact
+  //! Note that the contact won't show up in the roster until the server can
+  //! respond.  This happens async when the socket is being serviced
+  virtual XmppReturnStatus RequestRosterChange(
+    const XmppRosterContact* contact) = 0;
+
+  //! Request that the server remove a contact
+  //! The jabber protocol specifies that the server should also cancel any
+  //! subscriptions when this is done.  Like adding, this contact won't be
+  //! removed until the server responds.
+  virtual XmppReturnStatus RequestRosterRemove(const Jid& jid) = 0;
+
+  // SUBSCRIPTION MANAGEMENT ---------------------------------------------------
+
+  //! Request a subscription to presence notifications form a Jid
+  virtual XmppReturnStatus RequestSubscription(const Jid& jid) = 0;
+
+  //! Cancel a subscription to presence notifications from a Jid
+  virtual XmppReturnStatus CancelSubscription(const Jid& jid) = 0;
+
+  //! Approve a request to deliver presence notifications to a jid
+  virtual XmppReturnStatus ApproveSubscriber(const Jid& jid) = 0;
+
+  //! Deny or cancel presence notification deliver to a jid
+  virtual XmppReturnStatus CancelSubscriber(const Jid& jid) = 0;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/rostermoduleimpl.cc b/talk/xmpp/rostermoduleimpl.cc
new file mode 100644
index 0000000..c5da5a9
--- /dev/null
+++ b/talk/xmpp/rostermoduleimpl.cc
@@ -0,0 +1,1041 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <vector>
+#include <string>
+#include <map>
+#include <algorithm>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/stringencode.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/rostermoduleimpl.h"
+
+namespace buzz {
+
+// enum prase and persist helpers ----------------------------------------------
+static bool
+StringToPresenceShow(const std::string& input, XmppPresenceShow* show)
+{
+  // If this becomes a perf issue we can use a hash or a map here
+  if (STR_SHOW_AWAY == input)
+    *show = XMPP_PRESENCE_AWAY;
+  else if (STR_SHOW_DND == input)
+    *show = XMPP_PRESENCE_DND;
+  else if (STR_SHOW_XA == input)
+    *show = XMPP_PRESENCE_XA;
+  else if (STR_SHOW_CHAT == input)
+    *show = XMPP_PRESENCE_CHAT;
+  else if (STR_EMPTY == input)
+    *show = XMPP_PRESENCE_DEFAULT;
+  else
+    return false;
+
+  return true;
+}
+
+static bool
+PresenceShowToString(XmppPresenceShow show, const std::string ** output)
+{
+  switch(show) {
+    case XMPP_PRESENCE_AWAY:
+      *output = &STR_SHOW_AWAY;
+      return true;
+    case XMPP_PRESENCE_CHAT:
+      *output = &STR_SHOW_CHAT;
+      return true;
+    case XMPP_PRESENCE_XA:
+      *output = &STR_SHOW_XA;
+      return true;
+    case XMPP_PRESENCE_DND:
+      *output = &STR_SHOW_DND;
+      return true;
+    case XMPP_PRESENCE_DEFAULT:
+      *output = &STR_EMPTY;
+      return true;
+  }
+
+  *output = &STR_EMPTY;
+  return false;
+}
+
+static bool
+StringToSubscriptionState(const std::string& subscription,
+                          const std::string& ask,
+                          XmppSubscriptionState* state)
+{
+  if (ask == "subscribe")
+  {
+    if (subscription == "none") {
+      *state = XMPP_SUBSCRIPTION_NONE_ASKED;
+      return true;
+    }
+    if (subscription == "from") {
+      *state = XMPP_SUBSCRIPTION_FROM_ASKED;
+      return true;
+    }
+  } else if (ask == STR_EMPTY)
+  {
+    if (subscription == "none") {
+      *state = XMPP_SUBSCRIPTION_NONE;
+      return true;
+    }
+    if (subscription == "from") {
+      *state = XMPP_SUBSCRIPTION_FROM;
+      return true;
+    }
+    if (subscription == "to") {
+      *state = XMPP_SUBSCRIPTION_TO;
+      return true;
+    }
+    if (subscription == "both") {
+      *state = XMPP_SUBSCRIPTION_BOTH;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+static bool
+StringToSubscriptionRequestType(const std::string& string,
+                                XmppSubscriptionRequestType* type)
+{
+  if (string == "subscribe")
+    *type = XMPP_REQUEST_SUBSCRIBE;
+  else if (string == "unsubscribe")
+    *type = XMPP_REQUEST_UNSUBSCRIBE;
+  else if (string == "subscribed")
+    *type = XMPP_REQUEST_SUBSCRIBED;
+  else if (string == "unsubscribed")
+    *type = XMPP_REQUEST_UNSUBSCRIBED;
+  else
+    return false;
+  return true;
+}
+
+// XmppPresenceImpl class ------------------------------------------------------
+XmppPresence*
+XmppPresence::Create() {
+  return new XmppPresenceImpl();
+}
+
+XmppPresenceImpl::XmppPresenceImpl() {
+}
+
+const Jid
+XmppPresenceImpl::jid() const {
+  if (!raw_xml_.get())
+    return JID_EMPTY;
+
+  return Jid(raw_xml_->Attr(QN_FROM));
+}
+
+XmppPresenceAvailable
+XmppPresenceImpl::available() const {
+  if (!raw_xml_.get())
+    return XMPP_PRESENCE_UNAVAILABLE;
+
+  if (raw_xml_->Attr(QN_TYPE) == "unavailable")
+    return XMPP_PRESENCE_UNAVAILABLE;
+  else if (raw_xml_->Attr(QN_TYPE) == "error")
+    return XMPP_PRESENCE_ERROR;
+  else
+    return XMPP_PRESENCE_AVAILABLE;
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_available(XmppPresenceAvailable available) {
+  if (!raw_xml_.get())
+    CreateRawXmlSkeleton();
+
+  if (available == XMPP_PRESENCE_AVAILABLE)
+    raw_xml_->ClearAttr(QN_TYPE);
+  else if (available == XMPP_PRESENCE_UNAVAILABLE)
+    raw_xml_->SetAttr(QN_TYPE, "unavailable");
+  else if (available == XMPP_PRESENCE_ERROR)
+    raw_xml_->SetAttr(QN_TYPE, "error");
+  return XMPP_RETURN_OK;
+}
+
+XmppPresenceShow
+XmppPresenceImpl::presence_show() const {
+  if (!raw_xml_.get())
+    return XMPP_PRESENCE_DEFAULT;
+
+  XmppPresenceShow show;
+
+  if (StringToPresenceShow(raw_xml_->TextNamed(QN_SHOW), &show))
+    return show;
+
+  return XMPP_PRESENCE_DEFAULT;
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_presence_show(XmppPresenceShow show) {
+  if (!raw_xml_.get())
+    CreateRawXmlSkeleton();
+
+  const std::string* show_string;
+
+  if(!PresenceShowToString(show, &show_string))
+    return XMPP_RETURN_BADARGUMENT;
+
+  raw_xml_->ClearNamedChildren(QN_SHOW);
+
+  if (show!=XMPP_PRESENCE_DEFAULT) {
+    raw_xml_->AddElement(new XmlElement(QN_SHOW));
+    raw_xml_->AddText(*show_string, 1);
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+int
+XmppPresenceImpl::priority() const {
+  if (!raw_xml_.get())
+    return 0;
+
+  int raw_priority;
+  if (!talk_base::FromString(raw_xml_->TextNamed(QN_PRIORITY), &raw_priority))
+    raw_priority = 0;
+  if (raw_priority < -128)
+    raw_priority = -128;
+  if (raw_priority > 127)
+    raw_priority = 127;
+
+  return raw_priority;
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_priority(int priority) {
+  if (!raw_xml_.get())
+    CreateRawXmlSkeleton();
+
+  if (priority < -128 || priority > 127)
+    return XMPP_RETURN_BADARGUMENT;
+
+  raw_xml_->ClearNamedChildren(QN_PRIORITY);
+  if (0 != priority) {
+    std::string priority_string;
+    if (talk_base::ToString(priority, &priority_string)) {
+      raw_xml_->AddElement(new XmlElement(QN_PRIORITY));
+      raw_xml_->AddText(priority_string, 1);
+    }
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+const std::string&
+XmppPresenceImpl::status() const {
+  if (!raw_xml_.get())
+    return STR_EMPTY;
+
+  XmlElement* status_element;
+  XmlElement* element;
+
+  // Search for a status element with no xml:lang attribute on it.  if we can't
+  // find that then just return the first status element in the stanza.
+  for (status_element = element = raw_xml_->FirstNamed(QN_STATUS);
+       element;
+       element = element->NextNamed(QN_STATUS)) {
+    if (!element->HasAttr(QN_XML_LANG)) {
+      status_element = element;
+      break;
+    }
+  }
+
+  if (status_element) {
+    return status_element->BodyText();
+  }
+
+  return STR_EMPTY;
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_status(const std::string& status) {
+  if (!raw_xml_.get())
+    CreateRawXmlSkeleton();
+
+  raw_xml_->ClearNamedChildren(QN_STATUS);
+
+  if (status != STR_EMPTY) {
+    raw_xml_->AddElement(new XmlElement(QN_STATUS));
+    raw_xml_->AddText(status, 1);
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+const XmlElement*
+XmppPresenceImpl::raw_xml() const {
+  if (!raw_xml_.get())
+    const_cast<XmppPresenceImpl*>(this)->CreateRawXmlSkeleton();
+  return raw_xml_.get();
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_raw_xml(const XmlElement * xml) {
+  if (!xml ||
+      xml->Name() != QN_PRESENCE)
+    return XMPP_RETURN_BADARGUMENT;
+
+  const std::string& type = xml->Attr(QN_TYPE);
+  if (type != STR_EMPTY && type != "unavailable")
+    return XMPP_RETURN_BADARGUMENT;
+
+  raw_xml_.reset(new XmlElement(*xml));
+
+  return XMPP_RETURN_OK;
+}
+
+void
+XmppPresenceImpl::CreateRawXmlSkeleton() {
+  raw_xml_.reset(new XmlElement(QN_PRESENCE));
+}
+
+// XmppRosterContactImpl -------------------------------------------------------
+XmppRosterContact*
+XmppRosterContact::Create() {
+  return new XmppRosterContactImpl();
+}
+
+XmppRosterContactImpl::XmppRosterContactImpl() {
+  ResetGroupCache();
+}
+
+void
+XmppRosterContactImpl::SetXmlFromWire(const XmlElement* xml) {
+  ResetGroupCache();
+  if (xml)
+    raw_xml_.reset(new XmlElement(*xml));
+  else
+    raw_xml_.reset(NULL);
+}
+
+void
+XmppRosterContactImpl::ResetGroupCache() {
+  group_count_ = -1;
+  group_index_returned_ = -1;
+  group_returned_ = NULL;
+}
+
+const Jid
+XmppRosterContactImpl::jid() const {
+  return Jid(raw_xml_->Attr(QN_JID));
+}
+
+XmppReturnStatus
+XmppRosterContactImpl::set_jid(const Jid& jid)
+{
+  if (!raw_xml_.get())
+    CreateRawXmlSkeleton();
+
+  if (!jid.IsValid())
+    return XMPP_RETURN_BADARGUMENT;
+
+  raw_xml_->SetAttr(QN_JID, jid.Str());
+
+  return XMPP_RETURN_OK;
+}
+
+const std::string&
+XmppRosterContactImpl::name() const {
+  return raw_xml_->Attr(QN_NAME);
+}
+
+XmppReturnStatus
+XmppRosterContactImpl::set_name(const std::string& name) {
+  if (!raw_xml_.get())
+    CreateRawXmlSkeleton();
+
+  if (name == STR_EMPTY)
+    raw_xml_->ClearAttr(QN_NAME);
+  else
+    raw_xml_->SetAttr(QN_NAME, name);
+
+  return XMPP_RETURN_OK;
+}
+
+XmppSubscriptionState
+XmppRosterContactImpl::subscription_state() const {
+  if (!raw_xml_.get())
+    return XMPP_SUBSCRIPTION_NONE;
+
+  XmppSubscriptionState state;
+
+  if (StringToSubscriptionState(raw_xml_->Attr(QN_SUBSCRIPTION),
+                                raw_xml_->Attr(QN_ASK),
+                                &state))
+    return state;
+
+  return XMPP_SUBSCRIPTION_NONE;
+}
+
+size_t
+XmppRosterContactImpl::GetGroupCount() const {
+  if (!raw_xml_.get())
+    return 0;
+
+  if (-1 == group_count_) {
+    XmlElement *group_element = raw_xml_->FirstNamed(QN_ROSTER_GROUP);
+    int group_count = 0;
+    while(group_element) {
+      group_count++;
+      group_element = group_element->NextNamed(QN_ROSTER_GROUP);
+    }
+
+    ASSERT(group_count > 0); // protect the cast
+    XmppRosterContactImpl * me = const_cast<XmppRosterContactImpl*>(this);
+    me->group_count_ = group_count;
+  }
+
+  return group_count_;
+}
+
+const std::string&
+XmppRosterContactImpl::GetGroup(size_t index) const {
+  if (index >= GetGroupCount())
+    return STR_EMPTY;
+
+  // We cache the last group index and element that we returned.  This way
+  // going through the groups in order is order n and not n^2.  This could be
+  // enhanced if necessary by starting at the cached value if the index asked
+  // is after the cached one.
+  if (group_index_returned_ >= 0 &&
+      index == static_cast<size_t>(group_index_returned_) + 1)
+  {
+    XmppRosterContactImpl * me = const_cast<XmppRosterContactImpl*>(this);
+    me->group_returned_ = group_returned_->NextNamed(QN_ROSTER_GROUP);
+    ASSERT(group_returned_ != NULL);
+    me->group_index_returned_++;
+  } else if (group_index_returned_ < 0 ||
+             static_cast<size_t>(group_index_returned_) != index) {
+    XmlElement * group_element = raw_xml_->FirstNamed(QN_ROSTER_GROUP);
+    size_t group_index = 0;
+    while(group_index < index) {
+      ASSERT(group_element != NULL);
+      group_index++;
+      group_element = group_element->NextNamed(QN_ROSTER_GROUP);
+    }
+
+    XmppRosterContactImpl * me = const_cast<XmppRosterContactImpl*>(this);
+    me->group_index_returned_ = static_cast<int>(group_index);
+    me->group_returned_ = group_element;
+  }
+
+  return group_returned_->BodyText();
+}
+
+XmppReturnStatus
+XmppRosterContactImpl::AddGroup(const std::string& group) {
+  if (group == STR_EMPTY)
+    return XMPP_RETURN_BADARGUMENT;
+
+  if (!raw_xml_.get())
+    CreateRawXmlSkeleton();
+
+  if (FindGroup(group, NULL, NULL))
+    return XMPP_RETURN_OK;
+
+  raw_xml_->AddElement(new XmlElement(QN_ROSTER_GROUP));
+  raw_xml_->AddText(group, 1);
+  ++group_count_;
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppRosterContactImpl::RemoveGroup(const std::string& group) {
+  if (group == STR_EMPTY)
+    return XMPP_RETURN_BADARGUMENT;
+
+  if (!raw_xml_.get())
+    return XMPP_RETURN_OK;
+
+  XmlChild * child_before;
+  if (FindGroup(group, NULL, &child_before)) {
+    raw_xml_->RemoveChildAfter(child_before);
+    ResetGroupCache();
+  }
+  return XMPP_RETURN_OK;
+}
+
+bool
+XmppRosterContactImpl::FindGroup(const std::string& group,
+                                 XmlElement** element,
+                                 XmlChild** child_before) {
+  XmlChild * prev_child = NULL;
+  XmlChild * next_child;
+  XmlChild * child;
+  for (child = raw_xml_->FirstChild(); child; child = next_child) {
+    next_child = child->NextChild();
+    if (!child->IsText() &&
+        child->AsElement()->Name() == QN_ROSTER_GROUP &&
+        child->AsElement()->BodyText() == group) {
+      if (element)
+        *element = child->AsElement();
+      if (child_before)
+        *child_before = prev_child;
+      return true;
+    }
+    prev_child = child;
+  }
+
+  return false;
+}
+
+const XmlElement*
+XmppRosterContactImpl::raw_xml() const {
+  if (!raw_xml_.get())
+    const_cast<XmppRosterContactImpl*>(this)->CreateRawXmlSkeleton();
+  return raw_xml_.get();
+}
+
+XmppReturnStatus
+XmppRosterContactImpl::set_raw_xml(const XmlElement* xml) {
+  if (!xml ||
+      xml->Name() != QN_ROSTER_ITEM ||
+      xml->HasAttr(QN_SUBSCRIPTION) ||
+      xml->HasAttr(QN_ASK))
+    return XMPP_RETURN_BADARGUMENT;
+
+  ResetGroupCache();
+
+  raw_xml_.reset(new XmlElement(*xml));
+
+  return XMPP_RETURN_OK;
+}
+
+void
+XmppRosterContactImpl::CreateRawXmlSkeleton() {
+  raw_xml_.reset(new XmlElement(QN_ROSTER_ITEM));
+}
+
+// XmppRosterModuleImpl --------------------------------------------------------
+XmppRosterModule *
+XmppRosterModule::Create() {
+  return new XmppRosterModuleImpl();
+}
+
+XmppRosterModuleImpl::XmppRosterModuleImpl() :
+  roster_handler_(NULL),
+  incoming_presence_map_(new JidPresenceVectorMap()),
+  incoming_presence_vector_(new PresenceVector()),
+  contacts_(new ContactVector()) {
+
+}
+
+XmppRosterModuleImpl::~XmppRosterModuleImpl() {
+  DeleteIncomingPresence();
+  DeleteContacts();
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::set_roster_handler(XmppRosterHandler * handler) {
+  roster_handler_ = handler;
+  return XMPP_RETURN_OK;
+}
+
+XmppRosterHandler*
+XmppRosterModuleImpl::roster_handler() {
+  return roster_handler_;
+}
+
+XmppPresence*
+XmppRosterModuleImpl::outgoing_presence() {
+  return &outgoing_presence_;
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::BroadcastPresence() {
+  // Scrub the outgoing presence
+  const XmlElement* element = outgoing_presence_.raw_xml();
+
+  ASSERT(!element->HasAttr(QN_TO) &&
+         !element->HasAttr(QN_FROM) &&
+          (element->Attr(QN_TYPE) == STR_EMPTY ||
+           element->Attr(QN_TYPE) == "unavailable"));
+
+  if (!engine())
+    return XMPP_RETURN_BADSTATE;
+
+  return engine()->SendStanza(element);
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::SendDirectedPresence(const XmppPresence* presence,
+                                           const Jid& to_jid) {
+  if (!presence)
+    return XMPP_RETURN_BADARGUMENT;
+
+  if (!engine())
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement element(*(presence->raw_xml()));
+
+  if (element.Name() != QN_PRESENCE ||
+      element.HasAttr(QN_TO) ||
+      element.HasAttr(QN_FROM))
+    return XMPP_RETURN_BADARGUMENT;
+
+  if (element.HasAttr(QN_TYPE)) {
+    if (element.Attr(QN_TYPE) != STR_EMPTY &&
+        element.Attr(QN_TYPE) != "unavailable") {
+      return XMPP_RETURN_BADARGUMENT;
+    }
+  }
+
+  element.SetAttr(QN_TO, to_jid.Str());
+
+  return engine()->SendStanza(&element);
+}
+
+size_t
+XmppRosterModuleImpl::GetIncomingPresenceCount() {
+  return incoming_presence_vector_->size();
+}
+
+const XmppPresence*
+XmppRosterModuleImpl::GetIncomingPresence(size_t index) {
+  if (index >= incoming_presence_vector_->size())
+    return NULL;
+  return (*incoming_presence_vector_)[index];
+}
+
+size_t
+XmppRosterModuleImpl::GetIncomingPresenceForJidCount(const Jid& jid)
+{
+  // find the vector in the map
+  JidPresenceVectorMap::iterator pos;
+  pos = incoming_presence_map_->find(jid);
+  if (pos == incoming_presence_map_->end())
+    return 0;
+
+  ASSERT(pos->second != NULL);
+
+  return pos->second->size();
+}
+
+const XmppPresence*
+XmppRosterModuleImpl::GetIncomingPresenceForJid(const Jid& jid,
+                                                size_t index) {
+  JidPresenceVectorMap::iterator pos;
+  pos = incoming_presence_map_->find(jid);
+  if (pos == incoming_presence_map_->end())
+    return NULL;
+
+  ASSERT(pos->second != NULL);
+
+  if (index >= pos->second->size())
+    return NULL;
+
+  return (*pos->second)[index];
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::RequestRosterUpdate() {
+  if (!engine())
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement roster_get(QN_IQ);
+  roster_get.AddAttr(QN_TYPE, "get");
+  roster_get.AddAttr(QN_ID, engine()->NextId());
+  roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+  return engine()->SendIq(&roster_get, this, NULL);
+}
+
+size_t
+XmppRosterModuleImpl::GetRosterContactCount() {
+  return contacts_->size();
+}
+
+const XmppRosterContact*
+XmppRosterModuleImpl::GetRosterContact(size_t index) {
+  if (index >= contacts_->size())
+    return NULL;
+  return (*contacts_)[index];
+}
+
+class RosterPredicate {
+public:
+  explicit RosterPredicate(const Jid& jid) : jid_(jid) {
+  }
+
+  bool operator() (XmppRosterContactImpl *& contact) {
+    return contact->jid() == jid_;
+  }
+
+private:
+  Jid jid_;
+};
+
+const XmppRosterContact*
+XmppRosterModuleImpl::FindRosterContact(const Jid& jid) {
+  ContactVector::iterator pos;
+
+  pos = std::find_if(contacts_->begin(),
+                     contacts_->end(),
+                     RosterPredicate(jid));
+  if (pos == contacts_->end())
+    return NULL;
+
+  return *pos;
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::RequestRosterChange(
+  const XmppRosterContact* contact) {
+  if (!contact)
+    return XMPP_RETURN_BADARGUMENT;
+
+  Jid jid = contact->jid();
+
+  if (!jid.IsValid())
+    return XMPP_RETURN_BADARGUMENT;
+
+  if (!engine())
+    return XMPP_RETURN_BADSTATE;
+
+  const XmlElement* contact_xml = contact->raw_xml();
+  if (contact_xml->Name() != QN_ROSTER_ITEM ||
+      contact_xml->HasAttr(QN_SUBSCRIPTION) ||
+      contact_xml->HasAttr(QN_ASK))
+    return XMPP_RETURN_BADARGUMENT;
+
+  XmlElement roster_add(QN_IQ);
+  roster_add.AddAttr(QN_TYPE, "set");
+  roster_add.AddAttr(QN_ID, engine()->NextId());
+  roster_add.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+  roster_add.AddElement(new XmlElement(*contact_xml), 1);
+
+  return engine()->SendIq(&roster_add, this, NULL);
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::RequestRosterRemove(const Jid& jid) {
+  if (!jid.IsValid())
+    return XMPP_RETURN_BADARGUMENT;
+
+  if (!engine())
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement roster_add(QN_IQ);
+  roster_add.AddAttr(QN_TYPE, "set");
+  roster_add.AddAttr(QN_ID, engine()->NextId());
+  roster_add.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+  roster_add.AddAttr(QN_JID, jid.Str(), 1);
+  roster_add.AddAttr(QN_SUBSCRIPTION, "remove", 1);
+
+  return engine()->SendIq(&roster_add, this, NULL);
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::RequestSubscription(const Jid& jid) {
+  return SendSubscriptionRequest(jid, "subscribe");
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::CancelSubscription(const Jid& jid) {
+  return SendSubscriptionRequest(jid, "unsubscribe");
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::ApproveSubscriber(const Jid& jid) {
+  return SendSubscriptionRequest(jid, "subscribed");
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::CancelSubscriber(const Jid& jid) {
+  return SendSubscriptionRequest(jid, "unsubscribed");
+}
+
+void
+XmppRosterModuleImpl::IqResponse(XmppIqCookie, const XmlElement * stanza) {
+  // The only real Iq response that we expect to recieve are initial roster
+  // population
+  if (stanza->Attr(QN_TYPE) == "error")
+  {
+    if (roster_handler_)
+      roster_handler_->RosterError(this, stanza);
+
+    return;
+  }
+
+  ASSERT(stanza->Attr(QN_TYPE) == "result");
+
+  InternalRosterItems(stanza);
+}
+
+bool
+XmppRosterModuleImpl::HandleStanza(const XmlElement * stanza)
+{
+  ASSERT(engine() != NULL);
+
+  // There are two types of stanzas that we care about: presence and roster push
+  // Iqs
+  if (stanza->Name() == QN_PRESENCE) {
+    const std::string&  jid_string = stanza->Attr(QN_FROM);
+    Jid jid(jid_string);
+
+    if (!jid.IsValid())
+      return false; // if the Jid isn't valid, don't process
+
+    const std::string& type = stanza->Attr(QN_TYPE);
+    XmppSubscriptionRequestType request_type;
+    if (StringToSubscriptionRequestType(type, &request_type))
+      InternalSubscriptionRequest(jid, stanza, request_type);
+    else if (type == "unavailable" || type == STR_EMPTY)
+      InternalIncomingPresence(jid, stanza);
+    else if (type == "error")
+      InternalIncomingPresenceError(jid, stanza);
+    else
+      return false;
+
+    return true;
+  } else if (stanza->Name() == QN_IQ) {
+    const XmlElement * roster_query = stanza->FirstNamed(QN_ROSTER_QUERY);
+    if (!roster_query || stanza->Attr(QN_TYPE) != "set")
+      return false;
+
+    InternalRosterItems(stanza);
+
+    // respond to the IQ
+    XmlElement result(QN_IQ);
+    result.AddAttr(QN_TYPE, "result");
+    result.AddAttr(QN_TO, stanza->Attr(QN_FROM));
+    result.AddAttr(QN_ID, stanza->Attr(QN_ID));
+
+    engine()->SendStanza(&result);
+    return true;
+  }
+
+  return false;
+}
+
+void
+XmppRosterModuleImpl::DeleteIncomingPresence() {
+  // Clear out the vector of all presence notifications
+  {
+    PresenceVector::iterator pos;
+    for (pos = incoming_presence_vector_->begin();
+         pos < incoming_presence_vector_->end();
+         ++pos) {
+      XmppPresenceImpl * presence = *pos;
+      *pos = NULL;
+      delete presence;
+    }
+    incoming_presence_vector_->clear();
+  }
+
+  // Clear out all of the small presence vectors per Jid
+  {
+    JidPresenceVectorMap::iterator pos;
+    for (pos = incoming_presence_map_->begin();
+         pos != incoming_presence_map_->end();
+         ++pos) {
+      PresenceVector* presence_vector = pos->second;
+      pos->second = NULL;
+      delete presence_vector;
+    }
+    incoming_presence_map_->clear();
+  }
+}
+
+void
+XmppRosterModuleImpl::DeleteContacts() {
+  ContactVector::iterator pos;
+  for (pos = contacts_->begin();
+       pos < contacts_->end();
+       ++pos) {
+    XmppRosterContact* contact = *pos;
+    *pos = NULL;
+    delete contact;
+  }
+  contacts_->clear();
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::SendSubscriptionRequest(const Jid& jid,
+                                              const std::string& type) {
+  if (!jid.IsValid())
+    return XMPP_RETURN_BADARGUMENT;
+
+  if (!engine())
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement presence_request(QN_PRESENCE);
+  presence_request.AddAttr(QN_TO, jid.Str());
+  presence_request.AddAttr(QN_TYPE, type);
+
+  return engine()->SendStanza(&presence_request);
+}
+
+
+void
+XmppRosterModuleImpl::InternalSubscriptionRequest(const Jid& jid,
+                                                  const XmlElement* stanza,
+                                                  XmppSubscriptionRequestType
+                                                    request_type) {
+  if (roster_handler_)
+    roster_handler_->SubscriptionRequest(this, jid, request_type, stanza);
+}
+
+class PresencePredicate {
+public:
+  explicit PresencePredicate(const Jid& jid) : jid_(jid) {
+  }
+
+  bool operator() (XmppPresenceImpl *& contact) {
+    return contact->jid() == jid_;
+  }
+
+private:
+  Jid jid_;
+};
+
+void
+XmppRosterModuleImpl::InternalIncomingPresence(const Jid& jid,
+                                               const XmlElement* stanza) {
+  bool added = false;
+  Jid bare_jid = jid.BareJid();
+
+  // First add the presence to the map
+  JidPresenceVectorMap::iterator pos;
+  pos = incoming_presence_map_->find(jid.BareJid());
+  if (pos == incoming_presence_map_->end()) {
+    // Insert a new entry into the map.  Get the position of this new entry
+    pos = (incoming_presence_map_->insert(
+            std::make_pair(bare_jid, new PresenceVector()))).first;
+  }
+
+  PresenceVector * presence_vector = pos->second;
+  ASSERT(presence_vector != NULL);
+
+  // Try to find this jid in the bare jid bucket
+  PresenceVector::iterator presence_pos;
+  XmppPresenceImpl* presence;
+  presence_pos = std::find_if(presence_vector->begin(),
+                              presence_vector->end(),
+                              PresencePredicate(jid));
+
+  // Update/add it to the bucket
+  if (presence_pos == presence_vector->end()) {
+    presence = new XmppPresenceImpl();
+    if (XMPP_RETURN_OK == presence->set_raw_xml(stanza)) {
+      added = true;
+      presence_vector->push_back(presence);
+    } else {
+      delete presence;
+      presence = NULL;
+    }
+  } else {
+    presence = *presence_pos;
+    presence->set_raw_xml(stanza);
+  }
+
+  // now add to the comprehensive vector
+  if (added)
+    incoming_presence_vector_->push_back(presence);
+
+  // Call back to the user with the changed presence information
+  if (roster_handler_)
+    roster_handler_->IncomingPresenceChanged(this, presence);
+}
+
+
+void
+XmppRosterModuleImpl::InternalIncomingPresenceError(const Jid& jid,
+                                                    const XmlElement* stanza) {
+  if (roster_handler_)
+    roster_handler_->SubscriptionError(this, jid, stanza);
+}
+
+void
+XmppRosterModuleImpl::InternalRosterItems(const XmlElement* stanza) {
+  const XmlElement* result_data = stanza->FirstNamed(QN_ROSTER_QUERY);
+  if (!result_data)
+    return; // unknown stuff in result!
+
+  bool all_new = contacts_->empty();
+
+  for (const XmlElement* roster_item = result_data->FirstNamed(QN_ROSTER_ITEM);
+       roster_item;
+       roster_item = roster_item->NextNamed(QN_ROSTER_ITEM))
+  {
+    const std::string& jid_string = roster_item->Attr(QN_JID);
+    Jid jid(jid_string);
+    if (!jid.IsValid())
+      continue;
+
+    // This algorithm is N^2 on the number of incoming contacts after the
+    // initial load. There is no way to do this faster without allowing
+    // duplicates, introducing more data structures or write a custom data
+    // structure.  We'll see if this becomes a perf problem and fix it if it
+    // does.
+    ContactVector::iterator pos = contacts_->end();
+
+    if (!all_new) {
+      pos = std::find_if(contacts_->begin(),
+                         contacts_->end(),
+                         RosterPredicate(jid));
+    }
+
+    if (pos != contacts_->end()) { // Update/remove a current contact
+      if (roster_item->Attr(QN_SUBSCRIPTION) == "remove") {
+        XmppRosterContact* contact = *pos;
+        contacts_->erase(pos);
+        if (roster_handler_)
+          roster_handler_->ContactRemoved(this, contact,
+            std::distance(contacts_->begin(), pos));
+        delete contact;
+      } else {
+        XmppRosterContact* old_contact = *pos;
+        *pos = new XmppRosterContactImpl();
+        (*pos)->SetXmlFromWire(roster_item);
+        if (roster_handler_)
+          roster_handler_->ContactChanged(this, old_contact,
+            std::distance(contacts_->begin(), pos));
+        delete old_contact;
+      }
+    } else { // Add a new contact
+      XmppRosterContactImpl* contact = new XmppRosterContactImpl();
+      contact->SetXmlFromWire(roster_item);
+      contacts_->push_back(contact);
+      if (roster_handler_ && !all_new)
+        roster_handler_->ContactsAdded(this, contacts_->size() - 1, 1);
+    }
+  }
+
+  // Send a consolidated update if all contacts are new
+  if (roster_handler_ && all_new)
+    roster_handler_->ContactsAdded(this, 0, contacts_->size());
+}
+
+}
diff --git a/talk/xmpp/rostermoduleimpl.h b/talk/xmpp/rostermoduleimpl.h
new file mode 100644
index 0000000..48ad5e5
--- /dev/null
+++ b/talk/xmpp/rostermoduleimpl.h
@@ -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.
+ */
+
+#ifndef _rostermoduleimpl_h_
+#define _rostermoduleimpl_h_
+
+#include "talk/xmpp/moduleimpl.h"
+#include "talk/xmpp/rostermodule.h"
+
+namespace buzz {
+
+//! Presence Information
+//! This class stores both presence information for outgoing presence and is
+//! returned by methods in XmppRosterModule to represent recieved incoming
+//! presence information.  When this class is writeable (non-const) then each
+//! update to any property will set the inner xml.  Setting the raw_xml will
+//! rederive all of the other properties.
+class XmppPresenceImpl : public XmppPresence {
+public:
+  virtual ~XmppPresenceImpl() {}
+
+  //! The from Jid of for the presence information.
+  //! Typically this will be a full Jid with resource specified.  For outgoing
+  //! presence this should remain JID_NULL and will be scrubbed from the
+  //! stanza when being sent.
+  virtual const Jid jid() const;
+
+  //! Is the contact available?
+  virtual XmppPresenceAvailable available() const;
+
+  //! Sets if the user is available or not
+  virtual XmppReturnStatus set_available(XmppPresenceAvailable available);
+
+  //! The show value of the presence info
+  virtual XmppPresenceShow presence_show() const;
+
+  //! Set the presence show value
+  virtual XmppReturnStatus set_presence_show(XmppPresenceShow show);
+
+  //! The Priority of the presence info
+  virtual int priority() const;
+
+  //! Set the priority of the presence
+  virtual XmppReturnStatus set_priority(int priority);
+
+  //! The plain text status of the presence info.
+  //! If there are multiple status because of language, this will either be a
+  //! status that is not tagged for language or the first available
+  virtual const std::string& status() const;
+
+  //! Sets the status for the presence info.
+  //! If there is more than one status present already then this will remove
+  //! them all and replace it with one status element we no specified language
+  virtual XmppReturnStatus set_status(const std::string& status);
+
+  //! The raw xml of the presence update
+  virtual const XmlElement* raw_xml() const;
+
+  //! Sets the raw presence stanza for the presence update
+  //! This will cause all other data items in this structure to be rederived
+  virtual XmppReturnStatus set_raw_xml(const XmlElement * xml);
+
+private:
+  XmppPresenceImpl();
+
+  friend class XmppPresence;
+  friend class XmppRosterModuleImpl;
+
+  void CreateRawXmlSkeleton();
+
+  // Store everything in the XML element. If this becomes a perf issue we can
+  // cache the data.
+  talk_base::scoped_ptr<XmlElement> raw_xml_;
+};
+
+//! A contact as given by the server
+class XmppRosterContactImpl : public XmppRosterContact {
+public:
+  virtual ~XmppRosterContactImpl() {}
+
+  //! The jid for the contact.
+  //! Typically this will be a bare Jid.
+  virtual const Jid jid() const;
+
+  //! Sets the jid for the roster contact update
+  virtual XmppReturnStatus set_jid(const Jid& jid);
+
+  //! The name (nickname) stored for this contact
+  virtual const std::string& name() const;
+
+  //! Sets the name
+  virtual XmppReturnStatus set_name(const std::string& name);
+
+  //! The Presence subscription state stored on the server for this contact
+  //! This is never settable and will be ignored when generating a roster
+  //! add/update request
+  virtual XmppSubscriptionState subscription_state() const;
+
+  //! The number of Groups applied to this contact
+  virtual size_t GetGroupCount() const;
+
+  //! Gets a Group applied to the contact based on index.
+  virtual const std::string& GetGroup(size_t index) const;
+
+  //! Adds a group to this contact.
+  //! This will return a no error if the group is already present.
+  virtual XmppReturnStatus AddGroup(const std::string& group);
+
+  //! Removes a group from the contact.
+  //! This will return no error if the group isn't there
+  virtual XmppReturnStatus RemoveGroup(const std::string& group);
+
+  //! The raw xml for this roster contact
+  virtual const XmlElement* raw_xml() const;
+
+  //! Sets the raw presence stanza for the presence update
+  //! This will cause all other data items in this structure to be rederived
+  virtual XmppReturnStatus set_raw_xml(const XmlElement * xml);
+
+private:
+  XmppRosterContactImpl();
+
+  void CreateRawXmlSkeleton();
+  void SetXmlFromWire(const XmlElement * xml);
+  void ResetGroupCache();
+
+  bool FindGroup(const std::string& group,
+                 XmlElement** element,
+                 XmlChild** child_before);
+
+
+  friend class XmppRosterContact;
+  friend class XmppRosterModuleImpl;
+
+  int group_count_;
+  int group_index_returned_;
+  XmlElement * group_returned_;
+  talk_base::scoped_ptr<XmlElement> raw_xml_;
+};
+
+//! An XmppModule for handle roster and presence functionality
+class XmppRosterModuleImpl : public XmppModuleImpl,
+  public XmppRosterModule, public XmppIqHandler {
+public:
+  virtual ~XmppRosterModuleImpl();
+
+  IMPLEMENT_XMPPMODULE
+
+  //! Sets the roster handler (callbacks) for the module
+  virtual XmppReturnStatus set_roster_handler(XmppRosterHandler * handler);
+
+  //! Gets the roster handler for the module
+  virtual XmppRosterHandler* roster_handler();
+
+  // USER PRESENCE STATE -------------------------------------------------------
+
+  //! Gets the aggregate outgoing presence
+  //! This object is non-const and be edited directly.  No update is sent
+  //! to the server until a Broadcast is sent
+  virtual XmppPresence* outgoing_presence();
+
+  //! Broadcasts that the user is available.
+  //! Nothing with respect to presence is sent until this is called.
+  virtual XmppReturnStatus BroadcastPresence();
+
+  //! Sends a directed presence to a Jid
+  //! Note that the client doesn't store where directed presence notifications
+  //! have been sent.  The server can keep the appropriate state
+  virtual XmppReturnStatus SendDirectedPresence(const XmppPresence* presence,
+                                                const Jid& to_jid);
+
+  // INCOMING PRESENCE STATUS --------------------------------------------------
+
+  //! Returns the number of incoming presence data recorded
+  virtual size_t GetIncomingPresenceCount();
+
+  //! Returns an incoming presence datum based on index
+  virtual const XmppPresence* GetIncomingPresence(size_t index);
+
+  //! Gets the number of presence data for a bare Jid
+  //! There may be a datum per resource
+  virtual size_t GetIncomingPresenceForJidCount(const Jid& jid);
+
+  //! Returns a single presence data for a Jid based on index
+  virtual const XmppPresence* GetIncomingPresenceForJid(const Jid& jid,
+                                                        size_t index);
+
+  // ROSTER MANAGEMENT ---------------------------------------------------------
+
+  //! Requests an update of the roster from the server
+  //! This must be called to initialize the client side cache of the roster
+  //! After this is sent the server should keep this module apprised of any
+  //! changes.
+  virtual XmppReturnStatus RequestRosterUpdate();
+
+  //! Returns the number of contacts in the roster
+  virtual size_t GetRosterContactCount();
+
+  //! Returns a contact by index
+  virtual const XmppRosterContact* GetRosterContact(size_t index);
+
+  //! Finds a contact by Jid
+  virtual const XmppRosterContact* FindRosterContact(const Jid& jid);
+
+  //! Send a request to the server to add a contact
+  //! Note that the contact won't show up in the roster until the server can
+  //! respond.  This happens async when the socket is being serviced
+  virtual XmppReturnStatus RequestRosterChange(
+    const XmppRosterContact* contact);
+
+  //! Request that the server remove a contact
+  //! The jabber protocol specifies that the server should also cancel any
+  //! subscriptions when this is done.  Like adding, this contact won't be
+  //! removed until the server responds.
+  virtual XmppReturnStatus RequestRosterRemove(const Jid& jid);
+
+  // SUBSCRIPTION MANAGEMENT ---------------------------------------------------
+
+  //! Request a subscription to presence notifications form a Jid
+  virtual XmppReturnStatus RequestSubscription(const Jid& jid);
+
+  //! Cancel a subscription to presence notifications from a Jid
+  virtual XmppReturnStatus CancelSubscription(const Jid& jid);
+
+  //! Approve a request to deliver presence notifications to a jid
+  virtual XmppReturnStatus ApproveSubscriber(const Jid& jid);
+
+  //! Deny or cancel presence notification deliver to a jid
+  virtual XmppReturnStatus CancelSubscriber(const Jid& jid);
+
+  // XmppIqHandler IMPLEMENTATION ----------------------------------------------
+  virtual void IqResponse(XmppIqCookie cookie, const XmlElement * stanza);
+
+protected:
+  // XmppModuleImpl OVERRIDES --------------------------------------------------
+  virtual bool HandleStanza(const XmlElement *);
+
+  // PRIVATE DATA --------------------------------------------------------------
+private:
+  friend class XmppRosterModule;
+  XmppRosterModuleImpl();
+
+  // Helper functions
+  void DeleteIncomingPresence();
+  void DeleteContacts();
+  XmppReturnStatus SendSubscriptionRequest(const Jid& jid,
+                                           const std::string& type);
+  void InternalSubscriptionRequest(const Jid& jid, const XmlElement* stanza,
+                                   XmppSubscriptionRequestType request_type);
+  void InternalIncomingPresence(const Jid& jid, const XmlElement* stanza);
+  void InternalIncomingPresenceError(const Jid& jid, const XmlElement* stanza);
+  void InternalRosterItems(const XmlElement* stanza);
+
+  // Member data
+  XmppPresenceImpl outgoing_presence_;
+  XmppRosterHandler* roster_handler_;
+
+  typedef std::vector<XmppPresenceImpl*> PresenceVector;
+  typedef std::map<Jid, PresenceVector*> JidPresenceVectorMap;
+  talk_base::scoped_ptr<JidPresenceVectorMap> incoming_presence_map_;
+  talk_base::scoped_ptr<PresenceVector> incoming_presence_vector_;
+
+  typedef std::vector<XmppRosterContactImpl*> ContactVector;
+  talk_base::scoped_ptr<ContactVector> contacts_;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/saslcookiemechanism.h b/talk/xmpp/saslcookiemechanism.h
index 92cff4d..fd65b94 100644
--- a/talk/xmpp/saslcookiemechanism.h
+++ b/talk/xmpp/saslcookiemechanism.h
@@ -56,15 +56,14 @@
       token_service_("") {}
 
   virtual std::string GetMechanismName() { return mechanism_; }
-    
+
   virtual XmlElement * StartSaslAuth() {
     // send initial request
     XmlElement * el = new XmlElement(QN_SASL_AUTH, true);
     el->AddAttr(QN_MECHANISM, mechanism_);
     if (!token_service_.empty()) {
-      el->AddAttr(
-          QName(true, "http://www.google.com/talk/protocol/auth", "service"),
-          token_service_);
+      el->AddAttr(QName("http://www.google.com/talk/protocol/auth", "service"),
+                  token_service_);
     }
 
     std::string credential;
@@ -75,7 +74,7 @@
     el->AddText(Base64Encode(credential));
     return el;
   }
-  
+
 private:
   std::string mechanism_;
   std::string username_;
diff --git a/talk/xmpp/util_unittest.cc b/talk/xmpp/util_unittest.cc
new file mode 100644
index 0000000..91ecb69
--- /dev/null
+++ b/talk/xmpp/util_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright 2004 Google, Inc. All Rights Reserved.
+// Author: Joe Beda
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/util_unittest.h"
+
+namespace buzz {
+
+void XmppTestHandler::WriteOutput(const char * bytes, size_t len) {
+  output_ << std::string(bytes, len);
+}
+
+void XmppTestHandler::StartTls(const std::string & cname) {
+  output_ << "[START-TLS " << cname << "]";
+}
+
+void XmppTestHandler::CloseConnection() {
+  output_ << "[CLOSED]";
+}
+
+void XmppTestHandler::OnStateChange(int state) {
+  switch (static_cast<XmppEngine::State>(state)) {
+  case XmppEngine::STATE_START:
+    session_ << "[START]";
+    break;
+  case XmppEngine::STATE_OPENING:
+    session_ << "[OPENING]";
+    break;
+  case XmppEngine::STATE_OPEN:
+    session_ << "[OPEN]";
+    break;
+  case XmppEngine::STATE_CLOSED:
+    session_ << "[CLOSED]";
+    switch (engine_->GetError(NULL)) {
+    case XmppEngine::ERROR_NONE:
+      // do nothing
+      break;
+    case XmppEngine::ERROR_XML:
+      session_ << "[ERROR-XML]";
+      break;
+    case XmppEngine::ERROR_STREAM:
+      session_ << "[ERROR-STREAM]";
+      break;
+    case XmppEngine::ERROR_VERSION:
+      session_ << "[ERROR-VERSION]";
+      break;
+    case XmppEngine::ERROR_UNAUTHORIZED:
+      session_ << "[ERROR-UNAUTHORIZED]";
+      break;
+    case XmppEngine::ERROR_TLS:
+      session_ << "[ERROR-TLS]";
+      break;
+    case XmppEngine::ERROR_AUTH:
+      session_ << "[ERROR-AUTH]";
+      break;
+    case XmppEngine::ERROR_BIND:
+      session_ << "[ERROR-BIND]";
+      break;
+    case XmppEngine::ERROR_CONNECTION_CLOSED:
+      session_ << "[ERROR-CONNECTION-CLOSED]";
+      break;
+    case XmppEngine::ERROR_DOCUMENT_CLOSED:
+      session_ << "[ERROR-DOCUMENT-CLOSED]";
+      break;
+    }
+    break;
+  }
+}
+
+bool XmppTestHandler::HandleStanza(const XmlElement * stanza) {
+  stanza_ << stanza->Str();
+  return true;
+}
+
+std::string XmppTestHandler::OutputActivity() {
+  std::string result = output_.str();
+  output_.str("");
+  return result;
+}
+
+std::string XmppTestHandler::SessionActivity() {
+  std::string result = session_.str();
+  session_.str("");
+  return result;
+}
+
+std::string XmppTestHandler::StanzaActivity() {
+  std::string result = stanza_.str();
+  stanza_.str("");
+  return result;
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/util_unittest.h b/talk/xmpp/util_unittest.h
new file mode 100644
index 0000000..bb0656c
--- /dev/null
+++ b/talk/xmpp/util_unittest.h
@@ -0,0 +1,75 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_UTIL_UNITTEST_H_
+#define TALK_XMPP_UTIL_UNITTEST_H_
+
+#include <string>
+#include <sstream>
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+
+// This class captures callbacks from engine.
+class XmppTestHandler : public XmppOutputHandler,  public XmppSessionHandler,
+                        public XmppStanzaHandler {
+ public:
+  explicit XmppTestHandler(XmppEngine* engine) : engine_(engine) {}
+  virtual ~XmppTestHandler() {}
+
+  void SetEngine(XmppEngine* engine);
+
+  // Output handler
+  virtual void WriteOutput(const char * bytes, size_t len);
+  virtual void StartTls(const std::string & cname);
+  virtual void CloseConnection();
+
+  // Session handler
+  virtual void OnStateChange(int state);
+
+  // Stanza handler
+  virtual bool HandleStanza(const XmlElement* stanza);
+
+  std::string OutputActivity();
+  std::string SessionActivity();
+  std::string StanzaActivity();
+
+ private:
+  XmppEngine* engine_;
+  std::stringstream output_;
+  std::stringstream session_;
+  std::stringstream stanza_;
+};
+
+}  // namespace buzz
+
+inline std::ostream& operator<<(std::ostream& os, const buzz::Jid& jid) {
+  os << jid.Str();
+  return os;
+}
+
+#endif  // TALK_XMPP_UTIL_UNITTEST_H_
diff --git a/talk/xmpp/xmppclient.cc b/talk/xmpp/xmppclient.cc
index 3207ed5..563dc2a 100644
--- a/talk/xmpp/xmppclient.cc
+++ b/talk/xmpp/xmppclient.cc
@@ -27,11 +27,12 @@
 
 #include "xmppclient.h"
 #include "xmpptask.h"
-#include "talk/xmpp/constants.h"
 #include "talk/base/sigslot.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/xmpp/constants.h"
 #include "talk/xmpp/saslplainmechanism.h"
 #include "talk/xmpp/prexmppauth.h"
-#include "talk/base/scoped_ptr.h"
 #include "talk/xmpp/plainsaslhandler.h"
 
 namespace buzz {
@@ -82,8 +83,16 @@
   void OnSocketClosed();
 };
 
+bool IsTestServer(const std::string& server_name,
+                  const std::string& test_server_domain) {
+  return (!test_server_domain.empty() &&
+          talk_base::ends_with(server_name.c_str(),
+                               test_server_domain.c_str()));
+}
+
 XmppReturnStatus
-XmppClient::Connect(const XmppClientSettings & settings, const std::string & lang, AsyncSocket * socket, PreXmppAuth * pre_auth) {
+XmppClient::Connect(const XmppClientSettings & settings,
+    const std::string & lang, AsyncSocket * socket, PreXmppAuth * pre_auth) {
   if (socket == NULL)
     return XMPP_RETURN_BADARGUMENT;
   if (d_->socket_.get() != NULL)
@@ -101,7 +110,7 @@
   if (!settings.resource().empty()) {
     d_->engine_->SetRequestedResource(settings.resource());
   }
-  d_->engine_->SetUseTls(settings.use_tls());
+  d_->engine_->SetTls(settings.use_tls());
 
   // The talk.google.com server returns a certificate with common-name:
   //   CN="gmail.com" for @gmail.com accounts,
@@ -116,7 +125,8 @@
   if (server_name == buzz::STR_TALK_GOOGLE_COM ||
       server_name == buzz::STR_TALKX_L_GOOGLE_COM ||
       server_name == buzz::STR_XMPP_GOOGLE_COM ||
-      server_name == buzz::STR_XMPPX_L_GOOGLE_COM) {
+      server_name == buzz::STR_XMPPX_L_GOOGLE_COM ||
+      IsTestServer(server_name, settings.test_server_domain())) {
     if (settings.host() != STR_GMAIL_COM &&
         settings.host() != STR_GOOGLEMAIL_COM) {
       d_->engine_->SetTlsServer("", STR_TALK_GOOGLE_COM);
diff --git a/talk/xmpp/xmppclientsettings.h b/talk/xmpp/xmppclientsettings.h
index 4f821ab..29dfeec 100644
--- a/talk/xmpp/xmppclientsettings.h
+++ b/talk/xmpp/xmppclientsettings.h
@@ -30,13 +30,14 @@
 
 #include "talk/p2p/base/port.h"
 #include "talk/base/cryptstring.h"
+#include "talk/xmpp/xmppengine.h"
 
 namespace buzz {
 
 class XmppUserSettings {
  public:
   XmppUserSettings()
-    : use_tls_(false), 
+    : use_tls_(buzz::TLS_DISABLED),
       allow_plain_(false) {
   }
 
@@ -45,8 +46,11 @@
   void set_pass(const talk_base::CryptString & pass) { pass_ = pass; }
   void set_auth_cookie(const std::string & cookie) { auth_cookie_ = cookie; }
   void set_resource(const std::string & resource) { resource_ = resource; }
-  void set_use_tls(bool use_tls) { use_tls_ = use_tls; }
+  void set_use_tls(const TlsOptions use_tls) { use_tls_ = use_tls; }
   void set_allow_plain(bool f) { allow_plain_ = f; }
+  void set_test_server_domain(const std::string & test_server_domain) {
+    test_server_domain_ = test_server_domain;
+  }
   void set_token_service(const std::string & token_service) {
     token_service_ = token_service;
   }
@@ -56,8 +60,9 @@
   const talk_base::CryptString & pass() const { return pass_; }
   const std::string & auth_cookie() const { return auth_cookie_; }
   const std::string & resource() const { return resource_; }
-  bool use_tls() const { return use_tls_; }
+  TlsOptions use_tls() const { return use_tls_; }
   bool allow_plain() const { return allow_plain_; }
+  const std::string & test_server_domain() const { return test_server_domain_; }
   const std::string & token_service() const { return token_service_; }
 
  private:
@@ -66,8 +71,9 @@
   talk_base::CryptString pass_;
   std::string auth_cookie_;
   std::string resource_;
-  bool use_tls_;
+  TlsOptions use_tls_;
   bool allow_plain_;
+  std::string test_server_domain_;
   std::string token_service_;
 };
 
@@ -80,8 +86,8 @@
       use_proxy_auth_(false) {
   }
 
-  void set_server(const talk_base::SocketAddress & server) { 
-      server_ = server; 
+  void set_server(const talk_base::SocketAddress & server) {
+      server_ = server;
   }
   void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; }
   void set_proxy(talk_base::ProxyType f) { proxy_ = f; }
diff --git a/talk/xmpp/xmppengine.h b/talk/xmpp/xmppengine.h
index 44ed83d..e1b35a3 100644
--- a/talk/xmpp/xmppengine.h
+++ b/talk/xmpp/xmppengine.h
@@ -77,6 +77,14 @@
   XMPP_RETURN_NOTYETIMPLEMENTED,
 };
 
+// TlsOptions
+//    This is used by API to identify TLS setting.
+enum TlsOptions {
+  TLS_DISABLED,
+  TLS_ENABLED,
+  TLS_REQUIRED
+};
+
 //! Callback for socket output for an XmppEngine connection.
 //! Register via XmppEngine.SetOutputHandler.  An XmppEngine
 //! can call back to this handler while it is processing
@@ -197,7 +205,7 @@
   virtual XmppReturnStatus SetSaslHandler(SaslHandler * h) = 0;
 
   //! Sets whether TLS will be used within the connection (default true).
-  virtual XmppReturnStatus SetUseTls(bool useTls) = 0;
+  virtual XmppReturnStatus SetTls(TlsOptions useTls) = 0;
 
   //! Sets an alternate domain from which we allows TLS certificates.
   //! This is for use in the case where a we want to allow a proxy to
@@ -207,7 +215,7 @@
                                         const std::string & proxy_domain) = 0;
 
   //! Gets whether TLS will be used within the connection.
-  virtual bool GetUseTls() = 0;
+  virtual TlsOptions GetTls() = 0;
 
   //! Sets the request resource name, if any (optional).
   //! Note that the resource name may be overridden by the server; after
diff --git a/talk/xmpp/xmppengine_unittest.cc b/talk/xmpp/xmppengine_unittest.cc
new file mode 100644
index 0000000..46b79c6
--- /dev/null
+++ b/talk/xmpp/xmppengine_unittest.cc
@@ -0,0 +1,318 @@
+// Copyright 2004 Google Inc. All Rights Reserved
+// Author: David Bau
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/util_unittest.h"
+#include "talk/xmpp/saslplainmechanism.h"
+#include "talk/xmpp/plainsaslhandler.h"
+#include "talk/xmpp/xmppengine.h"
+
+using buzz::Jid;
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmppEngine;
+using buzz::XmppIqCookie;
+using buzz::XmppIqHandler;
+using buzz::XmppTestHandler;
+using buzz::QN_ID;
+using buzz::QN_IQ;
+using buzz::QN_TYPE;
+using buzz::QN_ROSTER_QUERY;
+using buzz::XMPP_RETURN_OK;
+using buzz::XMPP_RETURN_BADARGUMENT;
+
+// XmppEngineTestIqHandler
+//    This class grabs the response to an IQ stanza and stores it in a string.
+class XmppEngineTestIqHandler : public XmppIqHandler {
+ public:
+  virtual void IqResponse(XmppIqCookie, const XmlElement * stanza) {
+    ss_ << stanza->Str();
+  }
+
+  std::string IqResponseActivity() {
+    std::string result = ss_.str();
+    ss_.str("");
+    return result;
+  }
+
+ private:
+  std::stringstream ss_;
+};
+
+class XmppEngineTest : public testing::Test {
+ public:
+  XmppEngine* engine() { return engine_.get(); }
+  XmppTestHandler* handler() { return handler_.get(); }
+  virtual void SetUp() {
+    engine_.reset(XmppEngine::Create());
+    handler_.reset(new XmppTestHandler(engine_.get()));
+
+    Jid jid("david@my-server");
+    talk_base::InsecureCryptStringImpl pass;
+    pass.password() = "david";
+    engine_->SetSessionHandler(handler_.get());
+    engine_->SetOutputHandler(handler_.get());
+    engine_->AddStanzaHandler(handler_.get());
+    engine_->SetUser(jid);
+    engine_->SetSaslHandler(
+        new buzz::PlainSaslHandler(jid, talk_base::CryptString(pass), true));
+  }
+  virtual void TearDown() {
+    handler_.reset();
+    engine_.reset();
+  }
+  void RunLogin();
+
+ private:
+  talk_base::scoped_ptr<XmppEngine> engine_;
+  talk_base::scoped_ptr<XmppTestHandler> handler_;
+};
+
+void XmppEngineTest::RunLogin() {
+  // Connect
+  EXPECT_EQ(XmppEngine::STATE_START, engine()->GetState());
+  engine()->Connect();
+  EXPECT_EQ(XmppEngine::STATE_OPENING, engine()->GetState());
+
+  EXPECT_EQ("[OPENING]", handler_->SessionActivity());
+
+  EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" "
+           "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+           "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+
+  std::string input =
+    "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+    "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+    "xmlns=\"jabber:client\">";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  input =
+    "<stream:features>"
+      "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>"
+        "<required/>"
+      "</starttls>"
+      "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+        "<mechanism>DIGEST-MD5</mechanism>"
+        "<mechanism>PLAIN</mechanism>"
+      "</mechanisms>"
+    "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+  EXPECT_EQ("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>",
+      handler_->OutputActivity());
+
+  EXPECT_EQ("", handler_->SessionActivity());
+  EXPECT_EQ("", handler_->StanzaActivity());
+
+  input = "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";
+  engine()->HandleInput(input.c_str(), input.length());
+  EXPECT_EQ("[START-TLS my-server]"
+           "<stream:stream to=\"my-server\" xml:lang=\"*\" "
+           "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+           "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+
+  EXPECT_EQ("", handler_->SessionActivity());
+  EXPECT_EQ("", handler_->StanzaActivity());
+
+  input = "<stream:stream id=\"01234567\" version=\"1.0\" "
+          "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+          "xmlns=\"jabber:client\">";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  input =
+    "<stream:features>"
+      "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+        "<mechanism>DIGEST-MD5</mechanism>"
+        "<mechanism>PLAIN</mechanism>"
+      "</mechanisms>"
+    "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+  EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+      "mechanism=\"PLAIN\" "
+      "auth:allow-non-google-login=\"true\" "
+      "auth:client-uses-full-bind-result=\"true\" "
+      "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+      ">AGRhdmlkAGRhdmlk</auth>",
+      handler_->OutputActivity());
+
+  EXPECT_EQ("", handler_->SessionActivity());
+  EXPECT_EQ("", handler_->StanzaActivity());
+
+  input = "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>";
+  engine()->HandleInput(input.c_str(), input.length());
+  EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" "
+      "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+      "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+
+  EXPECT_EQ("", handler_->SessionActivity());
+  EXPECT_EQ("", handler_->StanzaActivity());
+
+  input = "<stream:stream id=\"01234567\" version=\"1.0\" "
+      "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+      "xmlns=\"jabber:client\">";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  input = "<stream:features>"
+          "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
+          "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
+          "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+  EXPECT_EQ("<iq type=\"set\" id=\"0\">"
+           "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/></iq>",
+           handler_->OutputActivity());
+
+  EXPECT_EQ("", handler_->SessionActivity());
+  EXPECT_EQ("", handler_->StanzaActivity());
+
+  input = "<iq type='result' id='0'>"
+          "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>"
+          "david@my-server/test</jid></bind></iq>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("<iq type=\"set\" id=\"1\">"
+           "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/></iq>",
+           handler_->OutputActivity());
+
+  EXPECT_EQ("", handler_->SessionActivity());
+  EXPECT_EQ("", handler_->StanzaActivity());
+
+  input = "<iq type='result' id='1'/>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[OPEN]", handler_->SessionActivity());
+  EXPECT_EQ("", handler_->StanzaActivity());
+  EXPECT_EQ(Jid("david@my-server/test"), engine()->FullJid());
+}
+
+// TestSuccessfulLogin()
+//    This function simply tests to see if a login works.  This includes
+//    encryption and authentication
+TEST_F(XmppEngineTest, TestSuccessfulLoginAndDisconnect) {
+  RunLogin();
+  engine()->Disconnect();
+  EXPECT_EQ("</stream:stream>[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppEngineTest, TestSuccessfulLoginAndConnectionClosed) {
+  RunLogin();
+  engine()->ConnectionClosed(0);
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-CONNECTION-CLOSED]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+
+// TestNotXmpp()
+//    This tests the error case when connecting to a non XMPP service
+TEST_F(XmppEngineTest, TestNotXmpp) {
+  // Connect
+  engine()->Connect();
+  EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" "
+          "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+          "xmlns=\"jabber:client\">\r\n", handler()->OutputActivity());
+
+  // Send garbage response (courtesy of apache)
+  std::string input = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[OPENING][CLOSED][ERROR-XML]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+// TestPassthrough()
+//    This tests that arbitrary stanzas can be passed to the server through
+//    the engine.
+TEST_F(XmppEngineTest, TestPassthrough) {
+  // Queue up an app stanza
+  XmlElement application_stanza(QName("test", "app-stanza"));
+  application_stanza.AddText("this-is-a-test");
+  engine()->SendStanza(&application_stanza);
+
+  // Do the whole login handshake
+  RunLogin();
+
+  EXPECT_EQ("<test:app-stanza xmlns:test=\"test\">this-is-a-test"
+          "</test:app-stanza>", handler()->OutputActivity());
+
+  // do another stanza
+  XmlElement roster_get(QN_IQ);
+  roster_get.AddAttr(QN_TYPE, "get");
+  roster_get.AddAttr(QN_ID, engine()->NextId());
+  roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+  engine()->SendStanza(&roster_get);
+  EXPECT_EQ("<iq type=\"get\" id=\"2\"><query xmlns=\"jabber:iq:roster\"/>"
+          "</iq>", handler()->OutputActivity());
+
+  // now say the server ends the stream
+  engine()->HandleInput("</stream:stream>", 16);
+  EXPECT_EQ("[CLOSED][ERROR-DOCUMENT-CLOSED]", handler()->SessionActivity());
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+// TestIqCallback()
+//    This tests the routing of Iq stanzas and responses.
+TEST_F(XmppEngineTest, TestIqCallback) {
+  XmppEngineTestIqHandler iq_response;
+  XmppIqCookie cookie;
+
+  // Do the whole login handshake
+  RunLogin();
+
+  // Build an iq request
+  XmlElement roster_get(QN_IQ);
+  roster_get.AddAttr(QN_TYPE, "get");
+  roster_get.AddAttr(QN_ID, engine()->NextId());
+  roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+  engine()->SendIq(&roster_get, &iq_response, &cookie);
+  EXPECT_EQ("<iq type=\"get\" id=\"2\"><query xmlns=\"jabber:iq:roster\"/>"
+          "</iq>", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+  EXPECT_EQ("", iq_response.IqResponseActivity());
+
+  // now say the server responds to the iq
+  std::string input = "<iq type='result' id='2'>"
+                      "<query xmlns='jabber:iq:roster'><item>foo</item>"
+                      "</query></iq>";
+  engine()->HandleInput(input.c_str(), input.length());
+  EXPECT_EQ("", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+  EXPECT_EQ("<cli:iq type=\"result\" id=\"2\" xmlns:cli=\"jabber:client\">"
+          "<query xmlns=\"jabber:iq:roster\"><item>foo</item></query>"
+          "</cli:iq>", iq_response.IqResponseActivity());
+
+  EXPECT_EQ(XMPP_RETURN_BADARGUMENT, engine()->RemoveIqHandler(cookie, NULL));
+
+  // Do it again with another id to test cancel
+  roster_get.SetAttr(QN_ID, engine()->NextId());
+  engine()->SendIq(&roster_get, &iq_response, &cookie);
+  EXPECT_EQ("<iq type=\"get\" id=\"3\"><query xmlns=\"jabber:iq:roster\"/>"
+          "</iq>", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+  EXPECT_EQ("", iq_response.IqResponseActivity());
+
+  // cancel the handler this time
+  EXPECT_EQ(XMPP_RETURN_OK, engine()->RemoveIqHandler(cookie, NULL));
+
+  // now say the server responds to the iq: the iq handler should not get it.
+  input = "<iq type='result' id='3'><query xmlns='jabber:iq:roster'><item>bar"
+          "</item></query></iq>";
+  engine()->HandleInput(input.c_str(), input.length());
+  EXPECT_EQ("<cli:iq type=\"result\" id=\"3\" xmlns:cli=\"jabber:client\">"
+          "<query xmlns=\"jabber:iq:roster\"><item>bar</item></query>"
+          "</cli:iq>", handler()->StanzaActivity());
+  EXPECT_EQ("", iq_response.IqResponseActivity());
+  EXPECT_EQ("", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+}
diff --git a/talk/xmpp/xmppengineimpl.cc b/talk/xmpp/xmppengineimpl.cc
index 262c531..9e21249 100644
--- a/talk/xmpp/xmppengineimpl.cc
+++ b/talk/xmpp/xmppengineimpl.cc
@@ -2,89 +2,84 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#define TRACK_ARRAY_ALLOC_PROBLEM
-
-#include <vector>
-#include <sstream>
-#include <algorithm>
-#include "talk/xmllite/xmlelement.h"
-#include "talk/base/common.h"
 #include "talk/xmpp/xmppengineimpl.h"
-#include "talk/xmpp/xmpplogintask.h"
-#include "talk/xmpp/constants.h"
+
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
 #include "talk/xmllite/xmlprinter.h"
+#include "talk/xmpp/constants.h"
 #include "talk/xmpp/saslhandler.h"
+#include "talk/xmpp/xmpplogintask.h"
 
 namespace buzz {
 
-static const std::string XMPP_CLIENT_NAMESPACES[] = {
-  "stream", "http://etherx.jabber.org/streams",
-  "", "jabber:client",
-};
-
-static const size_t XMPP_CLIENT_NAMESPACES_LEN = 4;
-
-XmppEngine * XmppEngine::Create() {
+XmppEngine* XmppEngine::Create() {
   return new XmppEngineImpl();
 }
 
 
-XmppEngineImpl::XmppEngineImpl() :
-    stanzaParseHandler_(this),
-    stanzaParser_(&stanzaParseHandler_),
-    engine_entered_(0),
-    user_jid_(JID_EMPTY),
-    password_(),
-    requested_resource_(STR_EMPTY),
-    tls_needed_(true),
-    login_task_(new XmppLoginTask(this)),
-    next_id_(0),
-    bound_jid_(JID_EMPTY),
-    state_(STATE_START),
-    encrypted_(false),
-    error_code_(ERROR_NONE),
-    subcode_(0),
-    stream_error_(NULL),
-    raised_reset_(false),
-    output_handler_(NULL),
-    session_handler_(NULL),
-    iq_entries_(new IqEntryVector()),
-    sasl_handler_(NULL),
-    output_(new std::stringstream()) {
+XmppEngineImpl::XmppEngineImpl()
+    : stanza_parse_handler_(this),
+      stanza_parser_(&stanza_parse_handler_),
+      engine_entered_(0),
+      password_(),
+      requested_resource_(STR_EMPTY),
+      tls_option_(buzz::TLS_REQUIRED),
+      login_task_(new XmppLoginTask(this)),
+      next_id_(0),
+      state_(STATE_START),
+      encrypted_(false),
+      error_code_(ERROR_NONE),
+      subcode_(0),
+      stream_error_(NULL),
+      raised_reset_(false),
+      output_handler_(NULL),
+      session_handler_(NULL),
+      iq_entries_(new IqEntryVector()),
+      sasl_handler_(NULL),
+      output_(new std::stringstream()) {
   for (int i = 0; i < HL_COUNT; i+= 1) {
     stanza_handlers_[i].reset(new StanzaHandlerVector());
   }
+
+  // Add XMPP namespaces to XML namespaces stack.
+  xmlns_stack_.AddXmlns("stream", "http://etherx.jabber.org/streams");
+  xmlns_stack_.AddXmlns("", "jabber:client");
 }
 
 XmppEngineImpl::~XmppEngineImpl() {
   DeleteIqCookies();
 }
 
-XmppReturnStatus
-XmppEngineImpl::SetOutputHandler(XmppOutputHandler* output_handler) {
+XmppReturnStatus XmppEngineImpl::SetOutputHandler(
+    XmppOutputHandler* output_handler) {
   if (state_ != STATE_START)
     return XMPP_RETURN_BADSTATE;
 
@@ -93,8 +88,8 @@
   return XMPP_RETURN_OK;
 }
 
-XmppReturnStatus
-XmppEngineImpl::SetSessionHandler(XmppSessionHandler* session_handler) {
+XmppReturnStatus XmppEngineImpl::SetSessionHandler(
+    XmppSessionHandler* session_handler) {
   if (state_ != STATE_START)
     return XMPP_RETURN_BADSTATE;
 
@@ -103,21 +98,20 @@
   return XMPP_RETURN_OK;
 }
 
-XmppReturnStatus
-XmppEngineImpl::HandleInput(const char * bytes, size_t len) {
+XmppReturnStatus XmppEngineImpl::HandleInput(
+    const char* bytes, size_t len) {
   if (state_ < STATE_OPENING || state_ > STATE_OPEN)
     return XMPP_RETURN_BADSTATE;
 
   EnterExit ee(this);
 
   // TODO: The return value of the xml parser is not checked.
-  stanzaParser_.Parse(bytes, len, false);
+  stanza_parser_.Parse(bytes, len, false);
 
   return XMPP_RETURN_OK;
 }
 
-XmppReturnStatus
-XmppEngineImpl::ConnectionClosed(int subcode) {
+XmppReturnStatus XmppEngineImpl::ConnectionClosed(int subcode) {
   if (state_ != STATE_CLOSED) {
     EnterExit ee(this);
     // If told that connection closed and not already closed,
@@ -131,19 +125,16 @@
   return XMPP_RETURN_OK;
 }
 
-XmppReturnStatus
-XmppEngineImpl::SetUseTls(bool useTls) {
+XmppReturnStatus XmppEngineImpl::SetTls(TlsOptions use_tls) {
   if (state_ != STATE_START)
     return XMPP_RETURN_BADSTATE;
-
-  tls_needed_ = useTls;
-
+  tls_option_ = use_tls;
   return XMPP_RETURN_OK;
 }
 
-XmppReturnStatus
-XmppEngineImpl::SetTlsServer(const std::string & tls_server_hostname,
-                             const std::string & tls_server_domain) {
+XmppReturnStatus XmppEngineImpl::SetTlsServer(
+    const std::string& tls_server_hostname,
+    const std::string& tls_server_domain) {
   if (state_ != STATE_START)
     return XMPP_RETURN_BADSTATE;
 
@@ -153,13 +144,11 @@
   return XMPP_RETURN_OK;
 }
 
-bool
-XmppEngineImpl::GetUseTls() {
-  return tls_needed_;
+TlsOptions XmppEngineImpl::GetTls() {
+  return tls_option_;
 }
 
-XmppReturnStatus
-XmppEngineImpl::SetUser(const Jid & jid) {
+XmppReturnStatus XmppEngineImpl::SetUser(const Jid& jid) {
   if (state_ != STATE_START)
     return XMPP_RETURN_BADSTATE;
 
@@ -168,13 +157,11 @@
   return XMPP_RETURN_OK;
 }
 
-const Jid &
-XmppEngineImpl::GetUser() {
+const Jid& XmppEngineImpl::GetUser() {
   return user_jid_;
 }
 
-XmppReturnStatus
-XmppEngineImpl::SetSaslHandler(SaslHandler * sasl_handler) {
+XmppReturnStatus XmppEngineImpl::SetSaslHandler(SaslHandler* sasl_handler) {
   if (state_ != STATE_START)
     return XMPP_RETURN_BADSTATE;
 
@@ -182,8 +169,8 @@
   return XMPP_RETURN_OK;
 }
 
-XmppReturnStatus
-XmppEngineImpl::SetRequestedResource(const std::string & resource) {
+XmppReturnStatus XmppEngineImpl::SetRequestedResource(
+    const std::string& resource) {
   if (state_ != STATE_START)
     return XMPP_RETURN_BADSTATE;
 
@@ -192,14 +179,13 @@
   return XMPP_RETURN_OK;
 }
 
-const std::string &
-XmppEngineImpl::GetRequestedResource() {
+const std::string& XmppEngineImpl::GetRequestedResource() {
   return requested_resource_;
 }
 
-XmppReturnStatus
-XmppEngineImpl::AddStanzaHandler(XmppStanzaHandler * stanza_handler,
-                                 XmppEngine::HandlerLevel level) {
+XmppReturnStatus XmppEngineImpl::AddStanzaHandler(
+    XmppStanzaHandler* stanza_handler,
+    XmppEngine::HandlerLevel level) {
   if (state_ == STATE_CLOSED)
     return XMPP_RETURN_BADSTATE;
 
@@ -208,9 +194,8 @@
   return XMPP_RETURN_OK;
 }
 
-XmppReturnStatus
-XmppEngineImpl::RemoveStanzaHandler(XmppStanzaHandler * stanza_handler) {
-
+XmppReturnStatus XmppEngineImpl::RemoveStanzaHandler(
+    XmppStanzaHandler* stanza_handler) {
   bool found = false;
 
   for (int level = 0; level < HL_COUNT; level += 1) {
@@ -225,15 +210,13 @@
     }
   }
 
-  if (!found) {
+  if (!found)
     return XMPP_RETURN_BADARGUMENT;
-  }
 
   return XMPP_RETURN_OK;
 }
 
-XmppReturnStatus
-XmppEngineImpl::Connect() {
+XmppReturnStatus XmppEngineImpl::Connect() {
   if (state_ != STATE_START)
     return XMPP_RETURN_BADSTATE;
 
@@ -250,8 +233,7 @@
   return XMPP_RETURN_OK;
 }
 
-XmppReturnStatus
-XmppEngineImpl::SendStanza(const XmlElement * element) {
+XmppReturnStatus XmppEngineImpl::SendStanza(const XmlElement* element) {
   if (state_ == STATE_CLOSED)
     return XMPP_RETURN_BADSTATE;
 
@@ -268,8 +250,7 @@
   return XMPP_RETURN_OK;
 }
 
-XmppReturnStatus
-XmppEngineImpl::SendRaw(const std::string & text) {
+XmppReturnStatus XmppEngineImpl::SendRaw(const std::string& text) {
   if (state_ == STATE_CLOSED || login_task_.get())
     return XMPP_RETURN_BADSTATE;
 
@@ -280,16 +261,13 @@
   return XMPP_RETURN_OK;
 }
 
-std::string
-XmppEngineImpl::NextId() {
+std::string XmppEngineImpl::NextId() {
   std::stringstream ss;
   ss << next_id_++;
   return ss.str();
 }
 
-XmppReturnStatus
-XmppEngineImpl::Disconnect() {
-
+XmppReturnStatus XmppEngineImpl::Disconnect() {
   if (state_ != STATE_CLOSED) {
     EnterExit ee(this);
     if (state_ == STATE_OPEN)
@@ -300,14 +278,13 @@
   return XMPP_RETURN_OK;
 }
 
-void
-XmppEngineImpl::IncomingStart(const XmlElement * pelStart) {
+void XmppEngineImpl::IncomingStart(const XmlElement* start) {
   if (HasError() || raised_reset_)
     return;
 
   if (login_task_.get()) {
     // start-stream should go to login task
-    login_task_->IncomingStanza(pelStart, true);
+    login_task_->IncomingStanza(start, true);
     if (login_task_->IsDone())
       login_task_.reset();
   }
@@ -317,8 +294,7 @@
   }
 }
 
-void
-XmppEngineImpl::IncomingStanza(const XmlElement * stanza) {
+void XmppEngineImpl::IncomingStanza(const XmlElement* stanza) {
   if (HasError() || raised_reset_)
     return;
 
@@ -342,7 +318,7 @@
     for (int level = HL_SINGLE; level <= HL_ALL; level += 1) {
       for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) {
         if ((*stanza_handlers_[level])[i]->HandleStanza(stanza))
-          goto Handled;
+          return;
       }
     }
 
@@ -350,29 +326,24 @@
     // Only do this for IQ stanzas as messages should probably just be dropped
     // and presence stanzas should certainly be dropped.
     std::string type = stanza->Attr(QN_TYPE);
-    if (stanza->Name() == QN_IQ && 
+    if (stanza->Name() == QN_IQ &&
         !(type == "error" || type == "result")) {
       SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY);
     }
   }
-  Handled:
-    ; // handled - we're done
 }
 
-void
-XmppEngineImpl::IncomingEnd(bool isError) {
+void XmppEngineImpl::IncomingEnd(bool isError) {
   if (HasError() || raised_reset_)
     return;
 
   SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0);
 }
 
-void
-XmppEngineImpl::InternalSendStart(const std::string & to) {
+void XmppEngineImpl::InternalSendStart(const std::string& to) {
   std::string hostname = tls_server_hostname_;
-  if (hostname.empty()) {
+  if (hostname.empty())
     hostname = to;
-  }
 
   // If not language is specified, the spec says use *
   std::string lang = lang_;
@@ -389,61 +360,56 @@
            << "xmlns=\"jabber:client\">\r\n";
 }
 
-void
-XmppEngineImpl::InternalSendStanza(const XmlElement * element) {
+void XmppEngineImpl::InternalSendStanza(const XmlElement* element) {
   // It should really never be necessary to set a FROM attribute on a stanza.
   // It is implied by the bind on the stream and if you get it wrong
   // (by flipping from/to on a message?) the server will close the stream.
   ASSERT(!element->HasAttr(QN_FROM));
 
-  // TODO: consider caching the XmlPrinter
-  XmlPrinter::PrintXml(output_.get(), element,
-            XMPP_CLIENT_NAMESPACES, XMPP_CLIENT_NAMESPACES_LEN);
+  XmlPrinter::PrintXml(output_.get(), element, &xmlns_stack_);
 }
 
-std::string
-XmppEngineImpl::ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) {
+std::string XmppEngineImpl::ChooseBestSaslMechanism(
+    const std::vector<std::string>& mechanisms, bool encrypted) {
   return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted);
 }
 
-SaslMechanism *
-XmppEngineImpl::GetSaslMechanism(const std::string & name) {
+SaslMechanism* XmppEngineImpl::GetSaslMechanism(const std::string& name) {
   return sasl_handler_->CreateSaslMechanism(name);
 }
 
-void
-XmppEngineImpl::SignalBound(const Jid & fullJid) {
+void XmppEngineImpl::SignalBound(const Jid& fullJid) {
   if (state_ == STATE_OPENING) {
     bound_jid_ = fullJid;
     state_ = STATE_OPEN;
   }
 }
 
-void
-XmppEngineImpl::SignalStreamError(const XmlElement * pelStreamError) {
+void XmppEngineImpl::SignalStreamError(const XmlElement* stream_error) {
   if (state_ != STATE_CLOSED) {
-    stream_error_.reset(new XmlElement(*pelStreamError));
+    stream_error_.reset(new XmlElement(*stream_error));
     SignalError(ERROR_STREAM, 0);
   }
 }
 
-void
-XmppEngineImpl::SignalError(Error errorCode, int subCode) {
+void XmppEngineImpl::SignalError(Error error_code, int sub_code) {
   if (state_ != STATE_CLOSED) {
-    error_code_ = errorCode;
-    subcode_ = subCode;
+    error_code_ = error_code;
+    subcode_ = sub_code;
     state_ = STATE_CLOSED;
   }
 }
 
-bool
-XmppEngineImpl::HasError() {
+bool XmppEngineImpl::HasError() {
   return error_code_ != ERROR_NONE;
 }
 
-void
-XmppEngineImpl::StartTls(const std::string & domain) {
+void XmppEngineImpl::StartTls(const std::string& domain) {
   if (output_handler_) {
+    // As substitute for the real (login jid's) domain, we permit
+    // verifying a tls_server_domain_ instead, if one was passed.
+    // This allows us to avoid running a proxy that needs to handle
+    // valuable certificates.
     output_handler_->StartTls(
       tls_server_domain_.empty() ? domain : tls_server_domain_);
     encrypted_ = true;
@@ -451,7 +417,7 @@
 }
 
 XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine)
-  : engine_(engine),
+    : engine_(engine),
   state_(engine->state_),
   error_(engine->error_code_) {
   engine->engine_entered_ += 1;
@@ -482,17 +448,17 @@
    return;
 
  if (engine->raised_reset_) {
-   engine->stanzaParser_.Reset();
+   engine->stanza_parser_.Reset();
    engine->raised_reset_ = false;
  }
 
  if (engine->session_handler_) {
    if (engine->state_ != state_)
      engine->session_handler_->OnStateChange(engine->state_);
-     // Note: Handling of OnStateChange(CLOSED) should allow for the
-     // deletion of the engine, so no members should be accessed
-     // after this line.
+   // Note: Handling of OnStateChange(CLOSED) should allow for the
+   // deletion of the engine, so no members should be accessed
+   // after this line.
  }
 }
 
-}
+}  // namespace buzz
diff --git a/talk/xmpp/xmppengineimpl.h b/talk/xmpp/xmppengineimpl.h
index e5d3dc6..e292e75 100644
--- a/talk/xmpp/xmppengineimpl.h
+++ b/talk/xmpp/xmppengineimpl.h
@@ -25,8 +25,8 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef _xmppengineimpl_h_
-#define _xmppengineimpl_h_
+#ifndef TALK_XMPP_XMPPENGINEIMPL_H_
+#define TALK_XMPP_XMPPENGINEIMPL_H_
 
 #include <sstream>
 #include <vector>
@@ -41,7 +41,6 @@
 class SaslHandler;
 class SaslMechanism;
 
-
 //! The XMPP connection engine.
 //! This engine implements the client side of the 'core' XMPP protocol.
 //! To use it, register an XmppOutputHandler to handle socket output
@@ -51,7 +50,7 @@
 //! An application can listen for events and receive stanzas by
 //! registering an XmppStanzaHandler via AddStanzaHandler().
 class XmppEngineImpl : public XmppEngine {
-public:
+ public:
   XmppEngineImpl();
   virtual ~XmppEngineImpl();
 
@@ -61,7 +60,7 @@
   virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh);
 
   //! Provides socket input to the engine
-  virtual XmppReturnStatus HandleInput(const char * bytes, size_t len);
+  virtual XmppReturnStatus HandleInput(const char* bytes, size_t len);
 
   //! Advises the engine that the socket has closed
   virtual XmppReturnStatus ConnectionClosed(int subcode);
@@ -69,26 +68,26 @@
   // SESSION SETUP ---------------------------------------------------------
 
   //! Indicates the (bare) JID for the user to use.
-  virtual XmppReturnStatus SetUser(const Jid & jid);
+  virtual XmppReturnStatus SetUser(const Jid& jid);
 
   //! Get the login (bare) JID.
-  virtual const Jid & GetUser();
+  virtual const Jid& GetUser();
 
   //! Indicates the autentication to use.  Takes ownership of the object.
-  virtual XmppReturnStatus SetSaslHandler(SaslHandler * sasl_handler);
+  virtual XmppReturnStatus SetSaslHandler(SaslHandler* sasl_handler);
 
   //! Sets whether TLS will be used within the connection (default true).
-  virtual XmppReturnStatus SetUseTls(bool useTls);
+  virtual XmppReturnStatus SetTls(TlsOptions use_tls);
 
   //! Sets an alternate domain from which we allows TLS certificates.
   //! This is for use in the case where a we want to allow a proxy to
   //! serve up its own certificate rather than one owned by the underlying
   //! domain.
-  virtual XmppReturnStatus SetTlsServer(const std::string & proxy_hostname,
-                                        const std::string & proxy_domain);
+  virtual XmppReturnStatus SetTlsServer(const std::string& proxy_hostname,
+                                        const std::string& proxy_domain);
 
   //! Gets whether TLS will be used within the connection.
-  virtual bool GetUseTls();
+  virtual TlsOptions GetTls();
 
   //! Sets the request resource name, if any (optional).
   //! Note that the resource name may be overridden by the server; after
@@ -96,10 +95,10 @@
   virtual XmppReturnStatus SetRequestedResource(const std::string& resource);
 
   //! Gets the request resource name.
-  virtual const std::string & GetRequestedResource();
+  virtual const std::string& GetRequestedResource();
 
   //! Sets language
-  virtual void SetLanguage(const std::string & lang) {
+  virtual void SetLanguage(const std::string& lang) {
     lang_ = lang;
   }
 
@@ -131,7 +130,7 @@
   //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM.
   //! Notice the stanza returned is owned by the XmppEngine and
   //! is deleted when the engine is destroyed.
-  virtual const XmlElement * GetStreamError() { return stream_error_.get(); }
+  virtual const XmlElement* GetStreamError() { return stream_error_.get(); }
 
   //! Closes down the connection.
   //! Sends CloseConnection to output, and disconnects and registered
@@ -151,14 +150,14 @@
   virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler);
 
   //! Sends a stanza to the server.
-  virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza);
+  virtual XmppReturnStatus SendStanza(const XmlElement* stanza);
 
   //! Sends raw text to the server
-  virtual XmppReturnStatus SendRaw(const std::string & text);
+  virtual XmppReturnStatus SendRaw(const std::string& text);
 
   //! Sends an iq to the server, and registers a callback for the result.
   //! Returns the cookie passed to the result handler.
-  virtual XmppReturnStatus SendIq(const XmlElement* pelStanza,
+  virtual XmppReturnStatus SendIq(const XmlElement* stanza,
                                   XmppIqHandler* iq_handler,
                                   XmppIqCookie* cookie);
 
@@ -170,55 +169,62 @@
   //! Forms and sends an error in response to the given stanza.
   //! Swaps to and from, sets type to "error", and adds error information
   //! based on the passed code.  Text is optional and may be STR_EMPTY.
-  virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+  virtual XmppReturnStatus SendStanzaError(const XmlElement* pelOriginal,
                                            XmppStanzaError code,
-                                           const std::string & text);
+                                           const std::string& text);
 
   //! The fullly bound JID.
   //! This JID is only valid after binding has succeeded.  If the value
   //! is JID_NULL, the binding has not succeeded.
-  virtual const Jid & FullJid() { return bound_jid_; }
+  virtual const Jid& FullJid() { return bound_jid_; }
 
   //! The next unused iq id for this connection.
   //! Call this when building iq stanzas, to ensure that each iq
   //! gets its own unique id.
   virtual std::string NextId();
 
-private:
+ private:
   friend class XmppLoginTask;
   friend class XmppIqEntry;
 
-  void IncomingStanza(const XmlElement *pelStanza);
-  void IncomingStart(const XmlElement *pelStanza);
+  void IncomingStanza(const XmlElement *stanza);
+  void IncomingStart(const XmlElement *stanza);
   void IncomingEnd(bool isError);
 
-  void InternalSendStart(const std::string & domainName);
-  void InternalSendStanza(const XmlElement * pelStanza);
-  std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted);
-  SaslMechanism * GetSaslMechanism(const std::string & name);
-  void SignalBound(const Jid & fullJid);
-  void SignalStreamError(const XmlElement * pelStreamError);
+  void InternalSendStart(const std::string& domainName);
+  void InternalSendStanza(const XmlElement* stanza);
+  std::string ChooseBestSaslMechanism(
+      const std::vector<std::string>& mechanisms, bool encrypted);
+  SaslMechanism* GetSaslMechanism(const std::string& name);
+  void SignalBound(const Jid& fullJid);
+  void SignalStreamError(const XmlElement* streamError);
   void SignalError(Error errorCode, int subCode);
   bool HasError();
   void DeleteIqCookies();
-  bool HandleIqResponse(const XmlElement * element);
-  void StartTls(const std::string & domain);
+  bool HandleIqResponse(const XmlElement* element);
+  void StartTls(const std::string& domain);
   void RaiseReset() { raised_reset_ = true; }
 
   class StanzaParseHandler : public XmppStanzaParseHandler {
-  public:
-    StanzaParseHandler(XmppEngineImpl * outer) : outer_(outer) {}
+   public:
+    StanzaParseHandler(XmppEngineImpl* outer) : outer_(outer) {}
     virtual ~StanzaParseHandler() {}
-    virtual void StartStream(const XmlElement * pelStream)
-      { outer_->IncomingStart(pelStream); }
-    virtual void Stanza(const XmlElement * pelStanza)
-      { outer_->IncomingStanza(pelStanza); }
-    virtual void EndStream()
-      { outer_->IncomingEnd(false); }
-    virtual void XmlError()
-      { outer_->IncomingEnd(true); }
-  private:
-    XmppEngineImpl * const outer_;
+
+    virtual void StartStream(const XmlElement* stream) {
+      outer_->IncomingStart(stream);
+    }
+    virtual void Stanza(const XmlElement* stanza) {
+      outer_->IncomingStanza(stanza);
+    }
+    virtual void EndStream() {
+      outer_->IncomingEnd(false);
+    }
+    virtual void XmlError() {
+      outer_->IncomingEnd(true);
+    }
+
+   private:
+    XmppEngineImpl* const outer_;
   };
 
   class EnterExit {
@@ -235,16 +241,15 @@
   friend class StanzaParseHandler;
   friend class EnterExit;
 
-  StanzaParseHandler stanzaParseHandler_;
-  XmppStanzaParser stanzaParser_;
-
+  StanzaParseHandler stanza_parse_handler_;
+  XmppStanzaParser stanza_parser_;
 
   // state
   int engine_entered_;
   Jid user_jid_;
   std::string password_;
   std::string requested_resource_;
-  bool tls_needed_;
+  TlsOptions tls_option_;
   std::string tls_server_hostname_;
   std::string tls_server_domain_;
   talk_base::scoped_ptr<XmppLoginTask> login_task_;
@@ -261,6 +266,8 @@
   XmppOutputHandler* output_handler_;
   XmppSessionHandler* session_handler_;
 
+  XmlnsStack xmlns_stack_;
+
   typedef std::vector<XmppStanzaHandler*> StanzaHandlerVector;
   talk_base::scoped_ptr<StanzaHandlerVector> stanza_handlers_[HL_COUNT];
 
@@ -272,7 +279,6 @@
   talk_base::scoped_ptr<std::stringstream> output_;
 };
 
-}
+}  // namespace buzz
 
-
-#endif
+#endif  // TALK_XMPP_XMPPENGINEIMPL_H_
diff --git a/talk/xmpp/xmpplogintask.cc b/talk/xmpp/xmpplogintask.cc
index 340d95d..726802d 100644
--- a/talk/xmpp/xmpplogintask.cc
+++ b/talk/xmpp/xmpplogintask.cc
@@ -25,8 +25,11 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "talk/xmpp/xmpplogintask.h"
+
 #include <string>
 #include <vector>
+
 #include "talk/base/base64.h"
 #include "talk/base/common.h"
 #include "talk/xmllite/xmlelement.h"
@@ -34,7 +37,6 @@
 #include "talk/xmpp/jid.h"
 #include "talk/xmpp/saslmechanism.h"
 #include "talk/xmpp/xmppengineimpl.h"
-#include "talk/xmpp/xmpplogintask.h"
 
 using talk_base::ConstantLabel;
 
@@ -56,10 +58,10 @@
   LASTLABEL
 };
 #endif  // _DEBUG
-
 XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
   pctx_(pctx),
   authNeeded_(true),
+  allowNonGoogleLogin_(true),
   state_(LOGINSTATE_INIT),
   pelStanza_(NULL),
   isStart_(false),
@@ -138,8 +140,14 @@
         if (!HandleFeatures(element))
           return Failure(XmppEngine::ERROR_VERSION);
 
-        // Use TLS if forced, or if available
-        if (pctx_->tls_needed_ || GetFeature(QN_TLS_STARTTLS) != NULL) {
+        bool tls_present = (GetFeature(QN_TLS_STARTTLS) != NULL);
+        // Error if TLS required but not present.
+        if (pctx_->tls_option_ == buzz::TLS_REQUIRED && !tls_present) {
+          return Failure(XmppEngine::ERROR_TLS);
+        }
+        // Use TLS if required or enabled, and also available
+        if ((pctx_->tls_option_ == buzz::TLS_REQUIRED ||
+            pctx_->tls_option_ == buzz::TLS_ENABLED) && tls_present) {
           state_ = LOGINSTATE_TLS_INIT;
           continue;
         }
@@ -176,7 +184,7 @@
         // to do so - see the implementation of XmppEngineImpl::StartTls and
         // XmppEngine::SetTlsServerDomain to see how you can use that feature
         pctx_->StartTls(pctx_->user_jid_.domain());
-        pctx_->tls_needed_ = false;
+        pctx_->tls_option_ = buzz::TLS_ENABLED;
         state_ = LOGINSTATE_INIT;
         continue;
       }
@@ -214,6 +222,16 @@
         if (auth == NULL) {
           return Failure(XmppEngine::ERROR_AUTH);
         }
+        if (allowNonGoogleLogin_) {
+          // Setting the following two attributes is required to support
+          // non-google ids.
+
+          // Allow login with non-google id accounts.
+          auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true");
+
+          // Allow login with either the non-google id or the friendly email.
+          auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true");
+        }
 
         pctx_->InternalSendStanza(auth);
         delete auth;
diff --git a/talk/xmpp/xmpplogintask.h b/talk/xmpp/xmpplogintask.h
index a6507b5..9b3f5ae 100644
--- a/talk/xmpp/xmpplogintask.h
+++ b/talk/xmpp/xmpplogintask.h
@@ -25,10 +25,12 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef _logintask_h_
-#define _logintask_h_
+#ifndef TALK_XMPP_LOGINTASK_H_
+#define TALK_XMPP_LOGINTASK_H_
 
 #include <string>
+#include <vector>
+
 #include "talk/base/logging.h"
 #include "talk/base/scoped_ptr.h"
 #include "talk/xmpp/jid.h"
@@ -41,6 +43,7 @@
 class SaslMechanism;
 
 
+// TODO: Rename to LoginTask.
 class XmppLoginTask {
 
 public:
@@ -51,6 +54,8 @@
     { return state_ == LOGINSTATE_DONE; }
   void IncomingStanza(const XmlElement * element, bool isStart);
   void OutgoingStanza(const XmlElement *element);
+  void set_allow_non_google_login(bool b)
+    { allowNonGoogleLogin_ = b; }
 
 private:
   enum LoginTaskState {
@@ -77,6 +82,7 @@
 
   XmppEngineImpl * pctx_;
   bool authNeeded_;
+  bool allowNonGoogleLogin_;
   LoginTaskState state_;
   const XmlElement * pelStanza_;
   bool isStart_;
@@ -95,4 +101,4 @@
 
 }
 
-#endif
+#endif  //  TALK_XMPP_LOGINTASK_H_
diff --git a/talk/xmpp/xmpplogintask_unittest.cc b/talk/xmpp/xmpplogintask_unittest.cc
new file mode 100644
index 0000000..51af81a
--- /dev/null
+++ b/talk/xmpp/xmpplogintask_unittest.cc
@@ -0,0 +1,614 @@
+// Copyright 2004 Google Inc. All Rights Reserved
+
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/util_unittest.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/saslplainmechanism.h"
+#include "talk/xmpp/plainsaslhandler.h"
+#include "talk/xmpp/xmppengine.h"
+
+using buzz::Jid;
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmppEngine;
+using buzz::XmppTestHandler;
+
+enum XlttStage {
+  XLTT_STAGE_CONNECT = 0,
+  XLTT_STAGE_STREAMSTART,
+  XLTT_STAGE_TLS_FEATURES,
+  XLTT_STAGE_TLS_PROCEED,
+  XLTT_STAGE_ENCRYPTED_START,
+  XLTT_STAGE_AUTH_FEATURES,
+  XLTT_STAGE_AUTH_SUCCESS,
+  XLTT_STAGE_AUTHENTICATED_START,
+  XLTT_STAGE_BIND_FEATURES,
+  XLTT_STAGE_BIND_SUCCESS,
+  XLTT_STAGE_SESSION_SUCCESS,
+};
+
+class XmppLoginTaskTest : public testing::Test {
+ public:
+  XmppEngine* engine() { return engine_.get(); }
+  XmppTestHandler* handler() { return handler_.get(); }
+  virtual void SetUp() {
+    engine_.reset(XmppEngine::Create());
+    handler_.reset(new XmppTestHandler(engine_.get()));
+
+    Jid jid("david@my-server");
+    talk_base::InsecureCryptStringImpl pass;
+    pass.password() = "david";
+    engine_->SetSessionHandler(handler_.get());
+    engine_->SetOutputHandler(handler_.get());
+    engine_->AddStanzaHandler(handler_.get());
+    engine_->SetUser(jid);
+    engine_->SetSaslHandler(
+        new buzz::PlainSaslHandler(jid, talk_base::CryptString(pass), true));
+  }
+  virtual void TearDown() {
+    handler_.reset();
+    engine_.reset();
+  }
+  void RunPartialLogin(XlttStage startstage, XlttStage endstage);
+  void SetTlsOptions(buzz::TlsOptions option);
+
+ private:
+  talk_base::scoped_ptr<XmppEngine> engine_;
+  talk_base::scoped_ptr<XmppTestHandler> handler_;
+};
+
+void XmppLoginTaskTest::SetTlsOptions(buzz::TlsOptions option) {
+  engine_->SetTls(option);
+}
+void XmppLoginTaskTest::RunPartialLogin(XlttStage startstage,
+                                        XlttStage endstage) {
+  std::string input;
+
+  switch (startstage) {
+    case XLTT_STAGE_CONNECT: {
+      engine_->Connect();
+      XmlElement appStanza(QName("test", "app-stanza"));
+      appStanza.AddText("this-is-a-test");
+      engine_->SendStanza(&appStanza);
+
+      EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" "
+          "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+          "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+      EXPECT_EQ("[OPENING]", handler_->SessionActivity());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      if (endstage == XLTT_STAGE_CONNECT)
+        return;
+    }
+
+    case XLTT_STAGE_STREAMSTART: {
+      input = "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+          "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+          "xmlns=\"jabber:client\">";
+      engine_->HandleInput(input.c_str(), input.length());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      EXPECT_EQ("", handler_->SessionActivity());
+      EXPECT_EQ("", handler_->OutputActivity());
+      if (endstage == XLTT_STAGE_STREAMSTART)
+        return;
+    }
+
+    case XLTT_STAGE_TLS_FEATURES: {
+      input = "<stream:features>"
+        "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
+       "</stream:features>";
+      engine_->HandleInput(input.c_str(), input.length());
+      EXPECT_EQ("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>",
+          handler_->OutputActivity());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      EXPECT_EQ("", handler_->SessionActivity());
+      if (endstage == XLTT_STAGE_TLS_FEATURES)
+        return;
+    }
+
+    case XLTT_STAGE_TLS_PROCEED: {
+      input = std::string("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+      engine_->HandleInput(input.c_str(), input.length());
+      EXPECT_EQ("[START-TLS my-server]"
+          "<stream:stream to=\"my-server\" xml:lang=\"*\" "
+          "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+          "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      EXPECT_EQ("", handler_->SessionActivity());
+       if (endstage == XLTT_STAGE_TLS_PROCEED)
+        return;
+    }
+
+    case XLTT_STAGE_ENCRYPTED_START: {
+      input = std::string("<stream:stream id=\"01234567\" version=\"1.0\" "
+          "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+          "xmlns=\"jabber:client\">");
+      engine_->HandleInput(input.c_str(), input.length());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      EXPECT_EQ("", handler_->SessionActivity());
+      EXPECT_EQ("", handler_->OutputActivity());
+      if (endstage == XLTT_STAGE_ENCRYPTED_START)
+        return;
+    }
+
+    case XLTT_STAGE_AUTH_FEATURES: {
+      input = "<stream:features>"
+        "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+          "<mechanism>DIGEST-MD5</mechanism>"
+          "<mechanism>PLAIN</mechanism>"
+        "</mechanisms>"
+       "</stream:features>";
+      engine_->HandleInput(input.c_str(), input.length());
+      EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+          "mechanism=\"PLAIN\" "
+          "auth:allow-non-google-login=\"true\" "
+          "auth:client-uses-full-bind-result=\"true\" "
+          "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+          ">AGRhdmlkAGRhdmlk</auth>",
+          handler_->OutputActivity());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      EXPECT_EQ("", handler_->SessionActivity());
+       if (endstage == XLTT_STAGE_AUTH_FEATURES)
+        return;
+    }
+
+    case XLTT_STAGE_AUTH_SUCCESS: {
+      input = "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>";
+      engine_->HandleInput(input.c_str(), input.length());
+      EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" "
+          "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+          "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      EXPECT_EQ("", handler_->SessionActivity());
+       if (endstage == XLTT_STAGE_AUTH_SUCCESS)
+        return;
+    }
+
+    case XLTT_STAGE_AUTHENTICATED_START: {
+      input = std::string("<stream:stream id=\"01234567\" version=\"1.0\" "
+          "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+          "xmlns=\"jabber:client\">");
+      engine_->HandleInput(input.c_str(), input.length());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      EXPECT_EQ("", handler_->SessionActivity());
+      EXPECT_EQ("", handler_->OutputActivity());
+      if (endstage == XLTT_STAGE_AUTHENTICATED_START)
+        return;
+    }
+
+    case XLTT_STAGE_BIND_FEATURES: {
+      input = "<stream:features>"
+          "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
+          "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
+        "</stream:features>";
+      engine_->HandleInput(input.c_str(), input.length());
+      EXPECT_EQ("<iq type=\"set\" id=\"0\">"
+          "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/></iq>",
+          handler_->OutputActivity());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      EXPECT_EQ("", handler_->SessionActivity());
+      if (endstage == XLTT_STAGE_BIND_FEATURES)
+        return;
+    }
+
+    case XLTT_STAGE_BIND_SUCCESS: {
+      input = "<iq type='result' id='0'>"
+          "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
+          "<jid>david@my-server/test</jid></bind></iq>";
+      engine_->HandleInput(input.c_str(), input.length());
+      EXPECT_EQ("<iq type=\"set\" id=\"1\">"
+          "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/></iq>",
+          handler_->OutputActivity());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      EXPECT_EQ("", handler_->SessionActivity());
+      if (endstage == XLTT_STAGE_BIND_SUCCESS)
+        return;
+    }
+
+    case XLTT_STAGE_SESSION_SUCCESS: {
+      input = "<iq type='result' id='1'/>";
+      engine_->HandleInput(input.c_str(), input.length());
+      EXPECT_EQ("<test:app-stanza xmlns:test=\"test\">this-is-a-test"
+          "</test:app-stanza>", handler_->OutputActivity());
+      EXPECT_EQ("[OPEN]", handler_->SessionActivity());
+      EXPECT_EQ("", handler_->StanzaActivity());
+      if (endstage == XLTT_STAGE_SESSION_SUCCESS)
+        return;
+    }
+  }
+}
+
+TEST_F(XmppLoginTaskTest, TestUtf8Good) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_CONNECT);
+
+  std::string input = "<?xml version='1.0' encoding='UTF-8'?>"
+      "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+      "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+      "xmlns=\"jabber:client\">";
+  engine()->HandleInput(input.c_str(), input.length());
+  EXPECT_EQ("", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestNonUtf8Bad) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_CONNECT);
+
+  std::string input = "<?xml version='1.0' encoding='ISO-8859-1'?>"
+      "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+      "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+      "xmlns=\"jabber:client\">";
+  engine()->HandleInput(input.c_str(), input.length());
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-XML]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestNoFeatures) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+  std::string input = "<iq type='get' id='1'/>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsRequiredNotPresent) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+  std::string input = "<stream:features>"
+      "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+        "<mechanism>DIGEST-MD5</mechanism>"
+        "<mechanism>PLAIN</mechanism>"
+      "</mechanisms>"
+     "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-TLS]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsRequeiredAndPresent) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+  std::string input = "<stream:features>"
+      "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>"
+        "<required/>"
+      "</starttls>"
+      "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+        "<mechanism>X-GOOGLE-TOKEN</mechanism>"
+        "<mechanism>PLAIN</mechanism>"
+        "<mechanism>X-OAUTH2</mechanism>"
+      "</mechanisms>"
+     "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>",
+      handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsEnabledNotPresent) {
+  SetTlsOptions(buzz::TLS_ENABLED);
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+  std::string input = "<stream:features>"
+      "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+        "<mechanism>DIGEST-MD5</mechanism>"
+        "<mechanism>PLAIN</mechanism>"
+      "</mechanisms>"
+     "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+      "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+      "auth:client-uses-full-bind-result=\"true\" "
+      "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+      ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsEnabledAndPresent) {
+  SetTlsOptions(buzz::TLS_ENABLED);
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+  std::string input = "<stream:features>"
+      "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+        "<mechanism>X-GOOGLE-TOKEN</mechanism>"
+        "<mechanism>PLAIN</mechanism>"
+        "<mechanism>X-OAUTH2</mechanism>"
+      "</mechanisms>"
+      "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+      "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+      "auth:client-uses-full-bind-result=\"true\" "
+      "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+      ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsDisabledNotPresent) {
+  SetTlsOptions(buzz::TLS_DISABLED);
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+    std::string input = "<stream:features>"
+      "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+        "<mechanism>DIGEST-MD5</mechanism>"
+        "<mechanism>PLAIN</mechanism>"
+      "</mechanisms>"
+     "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+      "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+      "auth:client-uses-full-bind-result=\"true\" "
+      "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+      ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsDisabledAndPresent) {
+  SetTlsOptions(buzz::TLS_DISABLED);
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+  std::string input = "<stream:features>"
+      "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+        "<mechanism>X-GOOGLE-TOKEN</mechanism>"
+        "<mechanism>PLAIN</mechanism>"
+        "<mechanism>X-OAUTH2</mechanism>"
+      "</mechanisms>"
+      "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+      "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+      "auth:client-uses-full-bind-result=\"true\" "
+      "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+      ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsFailure) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_TLS_FEATURES);
+
+  std::string input = "<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-TLS]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsBadStream) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_TLS_PROCEED);
+
+  std::string input = "<wrongtag>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestMissingSaslPlain) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_ENCRYPTED_START);
+
+  std::string input = "<stream:features>"
+        "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+          "<mechanism>DIGEST-MD5</mechanism>"
+        "</mechanisms>"
+       "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-AUTH]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestWrongPassword) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTH_FEATURES);
+
+  std::string input = "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-UNAUTHORIZED]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestAuthBadStream) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTH_SUCCESS);
+
+  std::string input = "<wrongtag>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestMissingBindFeature) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTHENTICATED_START);
+
+  std::string input = "<stream:features>"
+          "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
+        "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestMissingSessionFeature) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTHENTICATED_START);
+
+  std::string input = "<stream:features>"
+          "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
+        "</stream:features>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+/* TODO: Handle this case properly inside XmppLoginTask.
+TEST_F(XmppLoginTaskTest, TestBindFailure1) {
+  // check wrong JID
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+  std::string input = "<iq type='result' id='0'>"
+      "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
+      "<jid>davey@my-server/test</jid></bind></iq>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+*/
+
+TEST_F(XmppLoginTaskTest, TestBindFailure2) {
+  // check missing JID
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+  std::string input = "<iq type='result' id='0'>"
+      "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestBindFailure3) {
+  // check plain failure
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+  std::string input = "<iq type='error' id='0'/>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+  EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestBindFailure4) {
+  // check wrong id to ignore
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+  std::string input = "<iq type='error' id='1'/>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  // continue after an ignored iq
+  RunPartialLogin(XLTT_STAGE_BIND_SUCCESS, XLTT_STAGE_SESSION_SUCCESS);
+}
+
+TEST_F(XmppLoginTaskTest, TestSessionFailurePlain1) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_SUCCESS);
+
+  std::string input = "<iq type='error' id='1'/>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+  EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestSessionFailurePlain2) {
+  RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_SUCCESS);
+
+  // check reverse iq to ignore
+  // TODO: consider queueing or passing through?
+  std::string input = "<iq type='get' id='1'/>";
+  engine()->HandleInput(input.c_str(), input.length());
+
+  EXPECT_EQ("", handler()->OutputActivity());
+  EXPECT_EQ("", handler()->SessionActivity());
+
+  // continue after an ignored iq
+  RunPartialLogin(XLTT_STAGE_SESSION_SUCCESS, XLTT_STAGE_SESSION_SUCCESS);
+}
+
+TEST_F(XmppLoginTaskTest, TestBadXml) {
+  int errorKind = 0;
+  for (XlttStage stage = XLTT_STAGE_CONNECT;
+      stage <= XLTT_STAGE_SESSION_SUCCESS;
+      stage = static_cast<XlttStage>(stage + 1)) {
+    RunPartialLogin(XLTT_STAGE_CONNECT, stage);
+
+    std::string input;
+    switch (errorKind++ % 5) {
+      case 0: input = "&syntax;"; break;
+      case 1: input = "<nons:iq/>"; break;
+      case 2: input = "<iq a='b' a='dupe'/>"; break;
+      case 3: input = "<>"; break;
+      case 4: input = "<iq a='<wrong>'>"; break;
+    }
+
+    engine()->HandleInput(input.c_str(), input.length());
+
+    EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+    EXPECT_EQ("[CLOSED][ERROR-XML]", handler()->SessionActivity());
+
+    TearDown();
+    SetUp();
+  }
+}
+
+TEST_F(XmppLoginTaskTest, TestStreamError) {
+  for (XlttStage stage = XLTT_STAGE_CONNECT;
+      stage <= XLTT_STAGE_SESSION_SUCCESS;
+      stage = static_cast<XlttStage>(stage + 1)) {
+    switch (stage) {
+      case XLTT_STAGE_CONNECT:
+      case XLTT_STAGE_TLS_PROCEED:
+      case XLTT_STAGE_AUTH_SUCCESS:
+        continue;
+      default:
+        break;
+    }
+
+    RunPartialLogin(XLTT_STAGE_CONNECT, stage);
+
+    std::string input = "<stream:error>"
+        "<xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
+        "<text xml:lang='en' xmlns='urn:ietf:params:xml:ns:xmpp-streams'>"
+        "Some special application diagnostic information!"
+        "</text>"
+        "<escape-your-data xmlns='application-ns'/>"
+        "</stream:error>";
+
+    engine()->HandleInput(input.c_str(), input.length());
+
+    EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+    EXPECT_EQ("[CLOSED][ERROR-STREAM]", handler()->SessionActivity());
+
+    EXPECT_EQ("<str:error xmlns:str=\"http://etherx.jabber.org/streams\">"
+        "<xml-not-well-formed xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\"/>"
+        "<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\">"
+        "Some special application diagnostic information!"
+        "</text>"
+        "<escape-your-data xmlns=\"application-ns\"/>"
+        "</str:error>", engine()->GetStreamError()->Str());
+
+    TearDown();
+    SetUp();
+  }
+}
+
diff --git a/talk/xmpp/xmppstanzaparser_unittest.cc b/talk/xmpp/xmppstanzaparser_unittest.cc
new file mode 100644
index 0000000..06faf87
--- /dev/null
+++ b/talk/xmpp/xmppstanzaparser_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright 2004 Google Inc. All Rights Reserved
+
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmppstanzaparser.h"
+
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmppStanzaParser;
+using buzz::XmppStanzaParseHandler;
+
+class XmppStanzaParserTestHandler : public XmppStanzaParseHandler {
+ public:
+  virtual void StartStream(const XmlElement * element) {
+    ss_ << "START" << element->Str();
+  }
+  virtual void Stanza(const XmlElement * element) {
+    ss_ << "STANZA" << element->Str();
+  }
+  virtual void EndStream() {
+    ss_ << "END";
+  }
+  virtual void XmlError() {
+    ss_ << "ERROR";
+  }
+
+  std::string Str() {
+    return ss_.str();
+  }
+
+  std::string StrClear() {
+    std::string result = ss_.str();
+    ss_.str("");
+    return result;
+  }
+
+ private:
+  std::stringstream ss_;
+};
+
+
+TEST(XmppStanzaParserTest, TestTrivial) {
+  XmppStanzaParserTestHandler handler;
+  XmppStanzaParser parser(&handler);
+  std::string fragment;
+
+  fragment = "<trivial/>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("START<trivial/>END", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestStanzaAtATime) {
+  XmppStanzaParserTestHandler handler;
+  XmppStanzaParser parser(&handler);
+  std::string fragment;
+
+  fragment = "<stream:stream id='abc' xmlns='j:c' xmlns:stream='str'>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+      "xmlns:stream=\"str\"/>", handler.StrClear());
+
+  fragment = "<message type='foo'><body>hello</body></message>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("STANZA<c:message type=\"foo\" xmlns:c=\"j:c\">"
+      "<c:body>hello</c:body></c:message>", handler.StrClear());
+
+  fragment = " SOME TEXT TO IGNORE ";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("", handler.StrClear());
+
+  fragment = "<iq type='set' id='123'><abc xmlns='def'/></iq>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("STANZA<c:iq type=\"set\" id=\"123\" xmlns:c=\"j:c\">"
+      "<abc xmlns=\"def\"/></c:iq>", handler.StrClear());
+
+  fragment = "</stream:stream>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("END", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestFragmentedStanzas) {
+  XmppStanzaParserTestHandler handler;
+  XmppStanzaParser parser(&handler);
+  std::string fragment;
+
+  fragment = "<stream:stream id='abc' xmlns='j:c' xml";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("", handler.StrClear());
+
+  fragment = "ns:stream='str'><message type='foo'><body>hel";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+      "xmlns:stream=\"str\"/>", handler.StrClear());
+
+  fragment = "lo</body></message> IGNORE ME <iq type='set' id='123'>"
+      "<abc xmlns='def'/></iq></st";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("STANZA<c:message type=\"foo\" xmlns:c=\"j:c\">"
+      "<c:body>hello</c:body></c:message>STANZA<c:iq type=\"set\" id=\"123\" "
+      "xmlns:c=\"j:c\"><abc xmlns=\"def\"/></c:iq>", handler.StrClear());
+
+  fragment = "ream:stream>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("END", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestReset) {
+  XmppStanzaParserTestHandler handler;
+  XmppStanzaParser parser(&handler);
+  std::string fragment;
+
+  fragment = "<stream:stream id='abc' xmlns='j:c' xml";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("", handler.StrClear());
+
+  parser.Reset();
+  fragment = "<stream:stream id='abc' xmlns='j:c' xml";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("", handler.StrClear());
+
+  fragment = "ns:stream='str'><message type='foo'><body>hel";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+      "xmlns:stream=\"str\"/>", handler.StrClear());
+  parser.Reset();
+
+  fragment = "<stream:stream id='abc' xmlns='j:c' xmlns:stream='str'>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+      "xmlns:stream=\"str\"/>", handler.StrClear());
+
+  fragment = "<message type='foo'><body>hello</body></message>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("STANZA<c:message type=\"foo\" xmlns:c=\"j:c\">"
+      "<c:body>hello</c:body></c:message>", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestError) {
+  XmppStanzaParserTestHandler handler;
+  XmppStanzaParser parser(&handler);
+  std::string fragment;
+
+  fragment = "<-foobar/>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("ERROR", handler.StrClear());
+
+  parser.Reset();
+  fragment = "<stream:stream/>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("ERROR", handler.StrClear());
+  parser.Reset();
+
+  fragment = "ns:stream='str'><message type='foo'><body>hel";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("ERROR", handler.StrClear());
+  parser.Reset();
+
+  fragment = "<stream:stream xmlns:stream='st' xmlns='jc'>"
+      "<foo/><bar><st:foobar/></bar>";
+  parser.Parse(fragment.c_str(), fragment.length(), false);
+  EXPECT_EQ("START<stream:stream xmlns:stream=\"st\" xmlns=\"jc\"/>STANZA"
+      "<jc:foo xmlns:jc=\"jc\"/>ERROR", handler.StrClear());
+}
diff --git a/talk/xmpp/xmpptask.cc b/talk/xmpp/xmpptask.cc
index 60841d4..046f7a1 100644
--- a/talk/xmpp/xmpptask.cc
+++ b/talk/xmpp/xmpptask.cc
@@ -29,12 +29,9 @@
 #include "talk/xmpp/xmppclient.h"
 #include "talk/xmpp/xmppengine.h"
 #include "talk/xmpp/constants.h"
-#include "talk/xmpp/ratelimitmanager.h"
 
 namespace buzz {
 
-RateLimitManager task_rate_manager;
-
 XmppClientInterface::XmppClientInterface() {
 }
 
@@ -115,7 +112,7 @@
   XmlElement* result = new XmlElement(QN_IQ);
   if (!type.empty())
     result->AddAttr(QN_TYPE, type);
-  if (to != JID_EMPTY)
+  if (!to.IsEmpty())
     result->AddAttr(QN_TO, to.Str());
   if (!id.empty())
     result->AddAttr(QN_ID, id);
@@ -151,7 +148,7 @@
     return true;
 
   // We address the server as "", check if we are doing so here.
-  if (to != JID_EMPTY)
+  if (!to.IsEmpty())
     return false;
 
   // It is legal for the server to identify itself with "domain" or
@@ -175,10 +172,4 @@
   return true;
 }
 
-bool XmppTask::VerifyTaskRateLimit(const std::string task_name, int max_count, 
-                                   int per_x_seconds) {
-  return task_rate_manager.VerifyRateLimit(task_name, max_count, 
-                                           per_x_seconds);
-}
-
 }